File indexing completed on 2024-05-05 04:40:11

0001 /*
0002     SPDX-FileCopyrightText: 2006 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "patchhighlighter.h"
0008 
0009 #ifdef WITH_KOMPAREDIFF2_5_4_OR_NEWER
0010 #include <KompareDiff2/Difference>
0011 #include <KompareDiff2/DiffModel>
0012 #else
0013 #include <libkomparediff2/difference.h>
0014 #include <libkomparediff2/diffmodel.h>
0015 #endif
0016 
0017 #include "patchreview.h"
0018 #include "debug.h"
0019 
0020 #include <KColorScheme>
0021 #include <KIconEffect>
0022 #include <KLocalizedString>
0023 #include <KParts/MainWindow>
0024 #include <KTextEditor/View>
0025 #include <KTextEditor/Cursor>
0026 
0027 #include <KTextEditor/MovingInterface>
0028 #include <KTextEditor/MarkInterface>
0029 
0030 #include <interfaces/icore.h>
0031 #include <interfaces/idocument.h>
0032 #include <interfaces/iuicontroller.h>
0033 #include <language/highlighting/colorcache.h>
0034 #include <util/activetooltip.h>
0035 #include <sublime/message.h>
0036 
0037 #include <QApplication>
0038 #include <QPointer>
0039 #include <QTextBrowser>
0040 #include <QTextDocument>
0041 #include <QVBoxLayout>
0042 #include <QWidget>
0043 
0044 using namespace KDevelop;
0045 
0046 namespace
0047 {
0048 QPointer<QWidget> currentTooltip;
0049 KTextEditor::MovingRange* currentTooltipMark;
0050 
0051 
0052 QSize sizeHintForHtml( const QString& html, QSize maxSize ) {
0053     QTextDocument doc;
0054     doc.setHtml( html );
0055 
0056     QSize ret;
0057     if( doc.idealWidth() > maxSize.width() ) {
0058         doc.setPageSize( QSize( maxSize.width(), 30 ) );
0059         ret.setWidth( maxSize.width() );
0060     }else{
0061         ret.setWidth( doc.idealWidth() );
0062     }
0063     ret.setHeight( doc.size().height() );
0064     if( ret.height() > maxSize.height() )
0065         ret.setHeight( maxSize.height() );
0066     return ret;
0067 }
0068 
0069 }
0070 
0071 const unsigned int PatchHighlighter::m_allmarks =
0072     KTextEditor::MarkInterface::markType22 | KTextEditor::MarkInterface::markType23 |
0073     KTextEditor::MarkInterface::markType24 | KTextEditor::MarkInterface::markType25 |
0074     KTextEditor::MarkInterface::markType26 | KTextEditor::MarkInterface::markType27;
0075 
0076 void PatchHighlighter::showToolTipForMark(const QPoint& pos, KTextEditor::MovingRange* markRange)
0077 {
0078     if( currentTooltipMark == markRange && currentTooltip )
0079         return;
0080     delete currentTooltip;
0081 
0082     //Got the difference
0083     Diff2::Difference* diff = m_ranges[markRange];
0084 
0085     QString html;
0086 #if 0
0087     if( diff->hasConflict() )
0088         html += i18n( "<b><span style=\"color:red\">Conflict</span></b><br/>" );
0089 #endif
0090 
0091     Diff2::DifferenceStringList lines;
0092 
0093     html += QLatin1String("<b>");
0094     if( diff->applied() ) {
0095         if( !m_plugin->patch()->isAlreadyApplied() )
0096             html += i18n( "Applied.<br/>" );
0097 
0098         if( isInsertion( diff ) ) {
0099             html += i18n( "Insertion<br/>" );
0100         } else {
0101             if( isRemoval( diff ) )
0102                 html += i18n( "Removal<br/>" );
0103             html += i18n( "Previous:<br/>" );
0104             lines = diff->sourceLines();
0105         }
0106     } else {
0107         if( m_plugin->patch()->isAlreadyApplied() )
0108             html += i18n( "Reverted.<br/>" );
0109 
0110         if( isRemoval( diff ) ) {
0111             html += i18n( "Removal<br/>" );
0112         } else {
0113             if( isInsertion( diff ) )
0114                 html += i18n( "Insertion<br/>" );
0115 
0116             html += i18n( "Alternative:<br/>" );
0117 
0118             lines = diff->destinationLines();
0119         }
0120     }
0121     html += QLatin1String("</b>");
0122 
0123     for (auto* line : qAsConst(lines)) {
0124         uint currentPos = 0;
0125         const QString& string = line->string();
0126 
0127         const Diff2::MarkerList& markers = line->markerList();
0128 
0129         for (auto* marker : markers) {
0130             const QString spanText = string.mid( currentPos, marker->offset() - currentPos ).toHtmlEscaped();
0131             if (marker->type() == Diff2::Marker::End && (currentPos != 0 || marker->offset() != static_cast<uint>( string.size()))) {
0132                 html += QLatin1String("<b><span style=\"background:#FFBBBB\">") + spanText + QLatin1String("</span></b>");
0133             }else{
0134                 html += spanText;
0135             }
0136             currentPos = marker->offset();
0137         }
0138 
0139         html += string.mid(currentPos, string.length()-currentPos).toHtmlEscaped() + QLatin1String("<br/>");
0140     }
0141 
0142     auto browser = new QTextBrowser;
0143     browser->setPalette( QApplication::palette() );
0144     browser->setHtml( html );
0145 
0146     int maxHeight = 500;
0147 
0148     browser->setMinimumSize( sizeHintForHtml( html, QSize( ( ICore::self()->uiController()->activeMainWindow()->width()*2 )/3, maxHeight ) ) );
0149     browser->setMaximumSize( browser->minimumSize() + QSize( 10, 10 ) );
0150     if( browser->minimumHeight() != maxHeight )
0151         browser->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
0152 
0153     auto* layout = new QVBoxLayout;
0154     layout->setContentsMargins(0, 0, 0, 0);
0155     layout->addWidget( browser );
0156 
0157     auto* tooltip = new KDevelop::ActiveToolTip( ICore::self()->uiController()->activeMainWindow(), pos + QPoint( 5, -browser->sizeHint().height() - 30 ) );
0158     tooltip->setLayout( layout );
0159     tooltip->resize( tooltip->sizeHint() + QSize( 10, 10 ) );
0160     tooltip->move( pos - QPoint( 0, 20 + tooltip->height() ) );
0161     tooltip->setHandleRect( QRect( pos - QPoint( 15, 15 ), pos + QPoint( 15, 15 ) ) );
0162 
0163     currentTooltip = tooltip;
0164     currentTooltipMark = markRange;
0165 
0166     ActiveToolTip::showToolTip( tooltip );
0167 }
0168 
0169 void PatchHighlighter::markClicked( KTextEditor::Document* doc, const KTextEditor::Mark& mark, bool& handled ) {
0170     if( handled || !(mark.type & m_allmarks) )
0171         return;
0172 
0173     auto range_diff = rangeForMark(mark);
0174     m_applying = true;
0175 
0176     if (range_diff.first) {
0177         handled = true;
0178 
0179         KTextEditor::MovingRange *&range = range_diff.first;
0180         Diff2::Difference *&diff = range_diff.second;
0181 
0182         QString currentText = doc->text( range->toRange() );
0183 
0184         removeLineMarker( range );
0185 
0186         QString sourceText;
0187         QString targetText;
0188 
0189         for( int a = 0; a < diff->sourceLineCount(); ++a ) {
0190             sourceText += diff->sourceLineAt( a )->string();
0191             if (!sourceText.endsWith(QLatin1Char('\n')))
0192                 sourceText += QLatin1Char('\n');
0193         }
0194 
0195         for( int a = 0; a < diff->destinationLineCount(); ++a ) {
0196             targetText += diff->destinationLineAt( a )->string();
0197             if (!targetText.endsWith(QLatin1Char('\n')))
0198                 targetText += QLatin1Char('\n');
0199         }
0200 
0201         bool applied = diff->applied();
0202         QString &replace(applied ? targetText : sourceText);
0203         QString &replaceWith(applied ? sourceText : targetText);
0204 
0205         if( currentText.simplified() != replace.simplified() ) {
0206             const QString messageText = i18n("Could not apply the change: Text should be \"%1\", but is \"%2\".", replace, currentText);
0207             auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0208             ICore::self()->uiController()->postMessage(message);
0209 
0210             m_applying = false;
0211             return;
0212         }
0213 
0214         diff->apply(!applied);
0215 
0216         KTextEditor::Cursor start = range->start().toCursor();
0217         range->document()->replaceText( range->toRange(), replaceWith );
0218         const uint replaceWithLines = replaceWith.count(QLatin1Char('\n'));
0219         KTextEditor::Range newRange( start, KTextEditor::Cursor(start.line() +  replaceWithLines, start.column()) );
0220 
0221         range->setRange( newRange );
0222 
0223         addLineMarker( range, diff );
0224 
0225         {
0226             // After applying the change, show the tooltip again, mainly to update an old tooltip
0227             delete currentTooltip;
0228             currentTooltip = nullptr;
0229             bool h = false;
0230             markToolTipRequested( doc, mark, QCursor::pos(), h );
0231         }
0232     }
0233 
0234     m_applying = false;
0235 }
0236 
0237 QPair<KTextEditor::MovingRange*, Diff2::Difference*> PatchHighlighter::rangeForMark( const KTextEditor::Mark &mark ) {
0238     if (!m_applying) {
0239         for( QMap<KTextEditor::MovingRange*, Diff2::Difference*>::const_iterator it = m_ranges.constBegin(); it != m_ranges.constEnd(); ++it ) {
0240             if (it.value() && it.key()->start().line() <= mark.line && mark.line <= it.key()->end().line()) {
0241                 return qMakePair(it.key(), it.value());
0242             }
0243         }
0244     }
0245 
0246     return qMakePair(nullptr, nullptr);
0247 }
0248 
0249 void PatchHighlighter::markToolTipRequested( KTextEditor::Document*, const KTextEditor::Mark& mark, QPoint pos, bool& handled ) {
0250     if( handled )
0251         return;
0252 
0253     if( mark.type & m_allmarks ) {
0254         //There is a mark in this line. Show the old text.
0255         auto range = rangeForMark(mark);
0256         if( range.first ) {
0257             showToolTipForMark( pos, range.first );
0258             handled = true;
0259         }
0260     }
0261 }
0262 
0263 bool PatchHighlighter::isInsertion( Diff2::Difference* diff ) {
0264     return diff->sourceLineCount() == 0;
0265 }
0266 
0267 bool PatchHighlighter::isRemoval( Diff2::Difference* diff ) {
0268     return diff->destinationLineCount() == 0;
0269 }
0270 
0271 void PatchHighlighter::performContentChange( KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber ) {
0272     QPair<QList<Diff2::Difference*>, QList<Diff2::Difference*> > diffChange = m_model->linesChanged( oldLines, newLines, editLineNumber );
0273     const QList<Diff2::Difference*>& inserted = diffChange.first;
0274     const QList<Diff2::Difference*>& removed = diffChange.second;
0275 
0276     for (Diff2::Difference* d : removed) {
0277         const auto sourceLines = d->sourceLines();
0278         for (Diff2::DifferenceString* s : sourceLines)
0279             qCDebug(PLUGIN_PATCHREVIEW) << "removed source" << s->string();
0280         const auto destinationLines = d->destinationLines();
0281         for (Diff2::DifferenceString* s : destinationLines)
0282             qCDebug(PLUGIN_PATCHREVIEW) << "removed destination" << s->string();
0283     }
0284     for (Diff2::Difference* d : inserted) {
0285         const auto sourceLines = d->sourceLines();
0286         for (Diff2::DifferenceString* s : sourceLines)
0287             qCDebug(PLUGIN_PATCHREVIEW) << "inserted source" << s->string();
0288         const auto destinationLines = d->destinationLines();
0289         for (Diff2::DifferenceString* s : destinationLines)
0290             qCDebug(PLUGIN_PATCHREVIEW) << "inserted destination" << s->string();
0291     }
0292 
0293     // Remove all ranges that are in the same line (the line markers)
0294     for (auto it = m_ranges.begin(); it != m_ranges.end();) {
0295         if (removed.contains(it.value())) {
0296             KTextEditor::MovingRange* r = it.key();
0297             removeLineMarker(r); // is altering m_ranges
0298             it = m_ranges.erase(it);
0299 
0300             delete r;
0301         } else {
0302             ++it;
0303         }
0304     }
0305     qDeleteAll(removed);
0306 
0307     auto* moving = qobject_cast<KTextEditor::MovingInterface*>(doc);
0308     if ( !moving )
0309         return;
0310 
0311     for (Diff2::Difference* diff : inserted) {
0312         int lineStart = diff->destinationLineNumber();
0313         if ( lineStart > 0 ) {
0314             --lineStart;
0315         }
0316         int lineEnd = diff->destinationLineEnd();
0317         if ( lineEnd > 0 ) {
0318             --lineEnd;
0319         }
0320         KTextEditor::Range newRange( lineStart, 0, lineEnd, 0 );
0321         KTextEditor::MovingRange * r = moving->newMovingRange( newRange );
0322 
0323         m_ranges[r] = diff;
0324         addLineMarker( r, diff );
0325     }
0326 }
0327 
0328 void PatchHighlighter::textRemoved( KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& oldText ) {
0329     if ( m_applying ) { // Do not interfere with patch application
0330         return;
0331     }
0332     qCDebug(PLUGIN_PATCHREVIEW) << "removal range" << range;
0333     qCDebug(PLUGIN_PATCHREVIEW) << "removed text" << oldText;
0334 
0335     KTextEditor::Cursor cursor = range.start();
0336     int startLine = cursor.line();
0337     QStringList removedLines;
0338     QStringList remainingLines;
0339     if (startLine > 0) {
0340         QString above = doc->line(--startLine);
0341         removedLines << above;
0342         remainingLines << above;
0343     }
0344     const QString changed = doc->line(cursor.line()) + QLatin1Char('\n');
0345     removedLines << changed.midRef(0, cursor.column()) + oldText + changed.midRef(cursor.column());
0346     remainingLines << changed;
0347     if (doc->documentRange().end().line() > cursor.line()) {
0348         QString below = doc->line(cursor.line() + 1);
0349         removedLines << below;
0350         remainingLines << below;
0351     }
0352 
0353     performContentChange(doc, removedLines, remainingLines, startLine + 1);
0354 }
0355 
0356 void PatchHighlighter::newlineRemoved(KTextEditor::Document* doc, int line) {
0357     if ( m_applying ) { // Do not interfere with patch application
0358         return;
0359     }
0360     qCDebug(PLUGIN_PATCHREVIEW) << "remove newline" << line;
0361 
0362     KTextEditor::Cursor cursor = m_doc->cursorPosition();
0363 
0364     int startLine = line - 1;
0365     QStringList removedLines;
0366     QStringList remainingLines;
0367     if (startLine > 0) {
0368         QString above = doc->line(--startLine);
0369         removedLines << above;
0370         remainingLines << above;
0371     }
0372     QString changed = doc->line(line - 1);
0373     if (cursor.line() == line - 1) {
0374         removedLines << changed.mid(0, cursor.column());
0375         removedLines << changed.mid(cursor.column());
0376     } else {
0377         removedLines << changed;
0378         removedLines << QString();
0379     }
0380     remainingLines << changed;
0381     if (doc->documentRange().end().line() >= line) {
0382         QString below = doc->line(line);
0383         removedLines << below;
0384         remainingLines << below;
0385     }
0386 
0387     performContentChange(doc, removedLines, remainingLines, startLine + 1);
0388 }
0389 
0390 void PatchHighlighter::documentReloaded(KTextEditor::Document* doc)
0391 {
0392     qCDebug(PLUGIN_PATCHREVIEW) << "re-doing";
0393     //The document was loaded / reloaded
0394     if ( !m_model->differences() )
0395         return;
0396     auto* moving = qobject_cast<KTextEditor::MovingInterface*>(doc);
0397     if ( !moving )
0398         return;
0399 
0400     auto* markIface = qobject_cast<KTextEditor::MarkInterface*>(doc);
0401     if( !markIface )
0402         return;
0403 
0404     clear();
0405 
0406     constexpr int markPixmapSize = 32;
0407     KColorScheme scheme( QPalette::Active );
0408 
0409     QImage tintedInsertion = QIcon::fromTheme(QStringLiteral("insert-text")).pixmap(markPixmapSize, markPixmapSize).toImage();
0410     KIconEffect::colorize( tintedInsertion, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 );
0411     QImage tintedRemoval = QIcon::fromTheme(QStringLiteral("edit-delete")).pixmap(markPixmapSize, markPixmapSize).toImage();
0412     KIconEffect::colorize( tintedRemoval, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 );
0413     QImage tintedChange = QIcon::fromTheme(QStringLiteral("text-field")).pixmap(markPixmapSize, markPixmapSize).toImage();
0414     KIconEffect::colorize( tintedChange, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 );
0415 
0416     markIface->setMarkDescription( KTextEditor::MarkInterface::markType22, i18nc("@item", "Insertion") );
0417     markIface->setMarkPixmap( KTextEditor::MarkInterface::markType22, QPixmap::fromImage( tintedInsertion ) );
0418     markIface->setMarkDescription( KTextEditor::MarkInterface::markType23, i18nc("@item", "Removal") );
0419     markIface->setMarkPixmap( KTextEditor::MarkInterface::markType23, QPixmap::fromImage( tintedRemoval ) );
0420     markIface->setMarkDescription( KTextEditor::MarkInterface::markType24, i18nc("@item", "Change") );
0421     markIface->setMarkPixmap( KTextEditor::MarkInterface::markType24, QPixmap::fromImage( tintedChange ) );
0422 
0423     markIface->setMarkDescription( KTextEditor::MarkInterface::markType25, i18nc("@item", "Insertion" ) );
0424     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType25, QIcon::fromTheme(QStringLiteral("insert-text")).pixmap(markPixmapSize, markPixmapSize));
0425     markIface->setMarkDescription( KTextEditor::MarkInterface::markType26, i18nc("@item", "Removal") );
0426     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType26, QIcon::fromTheme(QStringLiteral("edit-delete")).pixmap(markPixmapSize, markPixmapSize));
0427     markIface->setMarkDescription( KTextEditor::MarkInterface::markType27, i18nc("@item", "Change") );
0428     markIface->setMarkPixmap(KTextEditor::MarkInterface::markType27, QIcon::fromTheme(QStringLiteral("text-field")).pixmap(markPixmapSize, markPixmapSize));
0429 
0430     for (Diff2::Difference* diff : qAsConst(*m_model->differences())) {
0431         int line, lineCount;
0432         Diff2::DifferenceStringList lines;
0433 
0434         if( diff->applied() ) {
0435             line = diff->destinationLineNumber();
0436             lineCount = diff->destinationLineCount();
0437             lines = diff->destinationLines();
0438         } else {
0439             line = diff->sourceLineNumber();
0440             lineCount = diff->sourceLineCount();
0441             lines = diff->sourceLines();
0442         }
0443 
0444         if ( line > 0 )
0445             line -= 1;
0446 
0447         KTextEditor::Cursor c( line, 0 );
0448         KTextEditor::Cursor endC( line + lineCount, 0 );
0449         if ( doc->lines() <= c.line() )
0450             c.setLine( doc->lines() - 1 );
0451         if ( doc->lines() <= endC.line() )
0452             endC.setLine( doc->lines() );
0453 
0454         if ( endC.isValid() && c.isValid() ) {
0455             KTextEditor::MovingRange * r = moving->newMovingRange( KTextEditor::Range( c, endC ) );
0456             m_ranges[r] = diff;
0457             addLineMarker( r, diff );
0458         }
0459     }
0460 }
0461 
0462 void PatchHighlighter::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) {
0463     if ( m_applying ) { // Do not interfere with patch application
0464         return;
0465     }
0466 
0467     int startLine = cursor.line();
0468     int endColumn = cursor.column() + text.length();
0469 
0470     qCDebug(PLUGIN_PATCHREVIEW) << "insertion range" <<
0471         KTextEditor::Range(cursor, KTextEditor::Cursor(startLine, endColumn));
0472     qCDebug(PLUGIN_PATCHREVIEW) << "inserted text" << text;
0473 
0474     QStringList removedLines;
0475     QStringList insertedLines;
0476     if (startLine > 0) {
0477         const QString above = doc->line(--startLine) + QLatin1Char('\n');
0478         removedLines << above;
0479         insertedLines << above;
0480     }
0481     const QString changed = doc->line(cursor.line()) + QLatin1Char('\n');
0482     removedLines << changed.midRef(0, cursor.column()) + changed.midRef(endColumn);
0483     insertedLines << changed;
0484     if (doc->documentRange().end().line() > cursor.line()) {
0485         const QString below = doc->line(cursor.line() + 1) + QLatin1Char('\n');
0486         removedLines << below;
0487         insertedLines << below;
0488     }
0489 
0490     performContentChange(doc, removedLines, insertedLines, startLine + 1);
0491 }
0492 
0493 void PatchHighlighter::newlineInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor)
0494 {
0495     if ( m_applying ) { // Do not interfere with patch application
0496         return;
0497     }
0498     qCDebug(PLUGIN_PATCHREVIEW) << "newline range" <<
0499         KTextEditor::Range(cursor, KTextEditor::Cursor(cursor.line() + 1, 0));
0500 
0501     int startLine = cursor.line();
0502     QStringList removedLines;
0503     QStringList insertedLines;
0504     if (startLine > 0) {
0505         const QString above = doc->line(--startLine) + QLatin1Char('\n');
0506         removedLines << above;
0507         insertedLines << above;
0508     }
0509     insertedLines << QStringLiteral("\n");
0510     if (doc->documentRange().end().line() > cursor.line()) {
0511         const QString below = doc->line(cursor.line() + 1) + QLatin1Char('\n');
0512         removedLines << below;
0513         insertedLines << below;
0514     }
0515 
0516     performContentChange(doc, removedLines, insertedLines, startLine + 1);
0517 }
0518 
0519 PatchHighlighter::PatchHighlighter( Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin, bool updatePatchFromEdits )
0520     : m_doc( kdoc ), m_plugin( plugin ), m_model( model ), m_applying( false ) {
0521     KTextEditor::Document* doc = kdoc->textDocument();
0522 //     connect( kdoc, SIGNAL(destroyed(QObject*)), this, SLOT(documentDestroyed()) );
0523     if (updatePatchFromEdits) {
0524         connect(doc, &KTextEditor::Document::textInserted, this, &PatchHighlighter::textInserted);
0525         connect(doc, &KTextEditor::Document::lineWrapped, this, &PatchHighlighter::newlineInserted);
0526         connect(doc, &KTextEditor::Document::textRemoved, this, &PatchHighlighter::textRemoved);
0527         connect(doc, &KTextEditor::Document::lineUnwrapped, this, &PatchHighlighter::newlineRemoved);
0528     }
0529     connect(doc, &KTextEditor::Document::reloaded, this, &PatchHighlighter::documentReloaded);
0530     connect(doc, &KTextEditor::Document::destroyed, this, &PatchHighlighter::documentDestroyed);
0531 
0532     if ( doc->lines() == 0 )
0533         return;
0534 
0535     if (qobject_cast<KTextEditor::MarkInterface*>(doc)) {
0536         //can't use new signal/slot syntax here, MarkInterface is not a QObject
0537         connect(doc, SIGNAL(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)),
0538                 this, SLOT(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)));
0539         connect(doc, SIGNAL(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)),
0540                 this, SLOT(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)));
0541     }
0542     if (qobject_cast<KTextEditor::MovingInterface*>(doc)) {
0543         //can't use new signal/slot syntax here, MovingInterface is not a QObject
0544         connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)),
0545                 this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)));
0546         connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)),
0547                 this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)));
0548     }
0549 
0550     documentReloaded(doc);
0551 }
0552 
0553 void PatchHighlighter::removeLineMarker( KTextEditor::MovingRange* range ) {
0554     auto* moving = qobject_cast<KTextEditor::MovingInterface*>(range->document());
0555     if ( !moving )
0556         return;
0557 
0558     auto* markIface = qobject_cast<KTextEditor::MarkInterface*>(range->document());
0559     if( !markIface )
0560         return;
0561 
0562     for (int line = range->start().line(); line <= range->end().line(); ++line) {
0563         markIface->removeMark(line, m_allmarks);
0564     }
0565 
0566     // Remove all ranges that are in the same line (the line markers)
0567     for (auto it = m_ranges.begin(); it != m_ranges.end();) {
0568         if (it.key() != range && range->overlaps(it.key()->toRange())) {
0569             delete it.key();
0570             it = m_ranges.erase(it);
0571         } else {
0572             ++it;
0573         }
0574     }
0575 }
0576 
0577 void PatchHighlighter::addLineMarker( KTextEditor::MovingRange* range, Diff2::Difference* diff ) {
0578     auto* moving = qobject_cast<KTextEditor::MovingInterface*>(range->document());
0579     if ( !moving )
0580         return;
0581 
0582     auto* markIface = qobject_cast<KTextEditor::MarkInterface*>(range->document());
0583     if( !markIface )
0584         return;
0585 
0586     KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() );
0587 
0588     bool isOriginalState = diff->applied() == m_plugin->patch()->isAlreadyApplied();
0589 
0590     if( isOriginalState ) {
0591         t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 0, 255, 255 ), 20 ) ) );
0592     }else{
0593         t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 255 ), 20 ) ) );
0594     }
0595     range->setAttribute( t );
0596     range->setZDepth( -500 );
0597 
0598     KTextEditor::MarkInterface::MarkTypes mark;
0599 
0600     if( isOriginalState ) {
0601         mark = KTextEditor::MarkInterface::markType27;
0602 
0603         if( isInsertion( diff ) )
0604             mark = KTextEditor::MarkInterface::markType25;
0605         if( isRemoval( diff ) )
0606             mark = KTextEditor::MarkInterface::markType26;
0607     }else{
0608         mark = KTextEditor::MarkInterface::markType24;
0609 
0610         if( isInsertion( diff ) )
0611             mark = KTextEditor::MarkInterface::markType22;
0612         if( isRemoval( diff ) )
0613             mark = KTextEditor::MarkInterface::markType23;
0614     }
0615 
0616     markIface->addMark( range->start().line(), mark );
0617 
0618     Diff2::DifferenceStringList lines;
0619     if( diff->applied() )
0620         lines = diff->destinationLines();
0621     else
0622         lines = diff->sourceLines();
0623 
0624     for( int a = 0; a < lines.size(); ++a ) {
0625         Diff2::DifferenceString* line = lines[a];
0626         int currentPos = 0;
0627         const uint lineLength = static_cast<uint>(line->string().size());
0628 
0629         const Diff2::MarkerList& markers = line->markerList();
0630 
0631         for (auto* marker : markers) {
0632             if (marker->type() == Diff2::Marker::End) {
0633                 if (currentPos != 0 || marker->offset() != lineLength) {
0634                     KTextEditor::MovingRange* r2 = moving->newMovingRange( KTextEditor::Range( KTextEditor::Cursor( a + range->start().line(), currentPos ), KTextEditor::Cursor( a + range->start().line(), marker->offset() ) ) );
0635                     m_ranges[r2] = nullptr;
0636 
0637                     KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() );
0638 
0639                     t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 0 ), 70 ) ) );
0640                     r2->setAttribute( t );
0641                     r2->setZDepth( -600 );
0642                 }
0643             }
0644             currentPos = marker->offset();
0645         }
0646     }
0647 }
0648 
0649 void PatchHighlighter::clear() {
0650     if( m_ranges.empty() )
0651         return;
0652 
0653     auto* moving = qobject_cast<KTextEditor::MovingInterface*>(m_doc->textDocument());
0654     if ( !moving )
0655         return;
0656 
0657     auto* markIface = qobject_cast<KTextEditor::MarkInterface*>(m_doc->textDocument());
0658     if( !markIface )
0659         return;
0660 
0661     const auto lines = markIface->marks().keys();
0662     for (int line : lines) {
0663         markIface->removeMark( line, m_allmarks );
0664     }
0665 
0666     // Diff is taking care of its own objects (except removed ones)
0667     qDeleteAll( m_ranges.keys() );
0668     m_ranges.clear();
0669 }
0670 
0671 PatchHighlighter::~PatchHighlighter() {
0672     clear();
0673 }
0674 
0675 IDocument* PatchHighlighter::doc() {
0676     return m_doc;
0677 }
0678 
0679 void PatchHighlighter::documentDestroyed() {
0680     qCDebug(PLUGIN_PATCHREVIEW) << "document destroyed";
0681     m_ranges.clear();
0682 }
0683 
0684 void PatchHighlighter::aboutToDeleteMovingInterfaceContent( KTextEditor::Document* ) {
0685     qCDebug(PLUGIN_PATCHREVIEW) << "about to delete";
0686     clear();
0687 }
0688 
0689 QList< KTextEditor::MovingRange* > PatchHighlighter::ranges() const
0690 {
0691     return m_ranges.keys();
0692 }
0693 
0694 #include "moc_patchhighlighter.cpp"