File indexing completed on 2025-01-05 05:14:51

0001 /*
0002 SPDX-FileCopyrightText: 2021 Hamed Masafi <hamed.masfi@gmail.com>
0003 
0004 SPDX-License-Identifier: GPL-3.0-or-later
0005 */
0006 
0007 #include "diffwidget.h"
0008 #include "codeeditor.h"
0009 #include <diff.h>
0010 
0011 #include <QScrollBar>
0012 #include <QTextBlock>
0013 
0014 DiffWidget::DiffWidget(QWidget *parent)
0015     : QWidget{parent}
0016     , mOldFile()
0017     , mNewFile()
0018 {
0019     setupUi(this);
0020     init();
0021 }
0022 
0023 DiffWidget::DiffWidget(QSharedPointer<Git::File> oldFile, QSharedPointer<Git::File> newFile, QWidget *parent)
0024     : QWidget{parent}
0025     , mOldFile(oldFile)
0026     , mNewFile(newFile)
0027 {
0028     setupUi(this);
0029     init();
0030 }
0031 
0032 void DiffWidget::init()
0033 {
0034     createPreviewWidget();
0035     segmentConnector->setMinimumWidth(80);
0036     segmentConnector->setMaximumWidth(80);
0037     segmentConnector->setLeft(leftCodeEditor);
0038     segmentConnector->setRight(rightCodeEditor);
0039 
0040     widgetSegmentsScrollBar->setSegmentConnector(segmentConnector);
0041 
0042     connect(leftCodeEditor, &CodeEditor::blockSelected, this, &DiffWidget::oldCodeEditor_blockSelected);
0043     connect(rightCodeEditor, &CodeEditor::blockSelected, this, &DiffWidget::newCodeEditor_blockSelected);
0044     connect(leftCodeEditor->verticalScrollBar(), &QScrollBar::valueChanged, this, &DiffWidget::oldCodeEditor_scroll);
0045     connect(rightCodeEditor->verticalScrollBar(), &QScrollBar::valueChanged, this, &DiffWidget::newCodeEditor_scroll);
0046     connect(splitter, &QSplitter::splitterMoved, this, &DiffWidget::slotSplitterSplitterMoved);
0047 
0048     recalculateInfoPaneSize();
0049 
0050     mDefaultOption = leftCodeEditor->document()->defaultTextOption();
0051 
0052     connect(widgetSegmentsScrollBar, &SegmentsScrollBar::hover, this, &DiffWidget::slotSegmentsScrollbarHover);
0053     connect(widgetSegmentsScrollBar, &SegmentsScrollBar::mouseEntered, mPreviewWidget, &QWidget::show);
0054     connect(widgetSegmentsScrollBar, &SegmentsScrollBar::mouseLeaved, mPreviewWidget, &QWidget::hide);
0055     showFilesInfo(true);
0056 }
0057 
0058 void DiffWidget::createPreviewWidget()
0059 {
0060     mPreviewWidget = new QWidget(this);
0061     auto layout = new QHBoxLayout(mPreviewWidget);
0062     mPreviewEditorLeft = new CodeEditor(mPreviewWidget);
0063     mPreviewEditorLeft->setShowFoldMarks(false);
0064     mPreviewEditorLeft->setShowTitleBar(false);
0065     mPreviewEditorLeft->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0066     mPreviewEditorLeft->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0067     layout->addWidget(mPreviewEditorLeft);
0068 
0069     mPreviewEditorRight = new CodeEditor(mPreviewWidget);
0070     mPreviewEditorRight->setShowFoldMarks(false);
0071     mPreviewEditorRight->setShowTitleBar(false);
0072     mPreviewEditorRight->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0073     mPreviewEditorRight->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0074     layout->addWidget(mPreviewEditorRight);
0075 
0076     layout->setContentsMargins(0, 0, 0, 0);
0077     layout->setSpacing(segmentConnector->width() + 2 * (splitter->handleWidth()));
0078     mPreviewWidget->setLayout(layout);
0079     mPreviewWidget->hide();
0080 }
0081 QSharedPointer<Git::File> DiffWidget::oldFile() const
0082 {
0083     return mOldFile;
0084 }
0085 
0086 void DiffWidget::setOldFileText(const QString &newOldFile)
0087 {
0088     leftCodeEditor->setTitle(newOldFile);
0089 }
0090 
0091 void DiffWidget::setOldFile(QSharedPointer<Git::File> newOldFile)
0092 {
0093     mOldFile = newOldFile;
0094     setOldFileText(newOldFile->displayName());
0095 }
0096 
0097 QSharedPointer<Git::File> DiffWidget::newFile() const
0098 {
0099     return mNewFile;
0100 }
0101 
0102 void DiffWidget::setNewFileText(const QString &newNewFile)
0103 {
0104     rightCodeEditor->setTitle(newNewFile);
0105 }
0106 
0107 void DiffWidget::setNewFile(QSharedPointer<Git::File> newNewFile)
0108 {
0109     mNewFile = newNewFile;
0110     setNewFileText(newNewFile->displayName());
0111 }
0112 
0113 void DiffWidget::compare()
0114 {
0115     if (!mOldFile || !mNewFile)
0116         return;
0117 
0118     const auto segments = Diff::diff(mOldFile->content(), mNewFile->content());
0119 
0120     leftCodeEditor->clearAll();
0121     rightCodeEditor->clearAll();
0122 
0123     mPreviewEditorLeft->clearAll();
0124     mPreviewEditorRight->clearAll();
0125 
0126     leftCodeEditor->setHighlighting(mOldFile->fileName());
0127     rightCodeEditor->setHighlighting(mNewFile->fileName());
0128 
0129     mPreviewEditorLeft->setHighlighting(mOldFile->fileName());
0130     mPreviewEditorRight->setHighlighting(mNewFile->fileName());
0131 
0132     segmentConnector->setSegments(segments);
0133     segmentConnector->update();
0134 
0135     for (const auto &s : segments) {
0136         CodeEditor::BlockType oldBlockType, newBlockType;
0137         switch (s->type) {
0138         case Diff::SegmentType::SameOnBoth:
0139             oldBlockType = newBlockType = CodeEditor::Unchanged;
0140             break;
0141         case Diff::SegmentType::OnlyOnLeft:
0142             oldBlockType = CodeEditor::Removed;
0143             newBlockType = CodeEditor::Added;
0144             break;
0145         case Diff::SegmentType::OnlyOnRight:
0146             oldBlockType = CodeEditor::Removed;
0147             newBlockType = CodeEditor::Added;
0148             break;
0149         case Diff::SegmentType::DifferentOnBoth:
0150             oldBlockType = newBlockType = CodeEditor::Edited;
0151             break;
0152         }
0153 
0154         if (mSameSize) {
0155             const int size = qMax(s->oldText.size(), s->newText.size());
0156             leftCodeEditor->append(s->oldText, oldBlockType, s, size);
0157             rightCodeEditor->append(s->newText, newBlockType, s, size);
0158 
0159             mPreviewEditorLeft->append(s->oldText, oldBlockType, s, size);
0160             mPreviewEditorRight->append(s->newText, newBlockType, s, size);
0161         } else {
0162             leftCodeEditor->append(s->oldText, oldBlockType, s);
0163             rightCodeEditor->append(s->newText, newBlockType, s);
0164 
0165             mPreviewEditorLeft->append(s->oldText, oldBlockType, s);
0166             mPreviewEditorRight->append(s->newText, newBlockType, s);
0167         }
0168     }
0169 
0170     scrollToTop();
0171 }
0172 
0173 void DiffWidget::showHiddenChars(bool show)
0174 {
0175     if (show) {
0176         auto n = mDefaultOption;
0177         n.setFlags(QTextOption::ShowTabsAndSpaces | QTextOption::ShowDocumentTerminator);
0178         leftCodeEditor->document()->setDefaultTextOption(n);
0179         rightCodeEditor->document()->setDefaultTextOption(n);
0180     } else {
0181         leftCodeEditor->document()->setDefaultTextOption(mDefaultOption);
0182         rightCodeEditor->document()->setDefaultTextOption(mDefaultOption);
0183     }
0184     leftCodeEditor->setWordWrapMode(QTextOption::NoWrap);
0185     rightCodeEditor->setWordWrapMode(QTextOption::NoWrap);
0186 }
0187 
0188 void DiffWidget::showFilesInfo(bool show)
0189 {
0190     leftCodeEditor->setShowTitleBar(show);
0191     rightCodeEditor->setShowTitleBar(show);
0192     segmentConnector->setTopMargin(show ? leftCodeEditor->titlebarHeight() + 2 : 0);
0193 }
0194 
0195 void DiffWidget::showSameSize(bool show)
0196 {
0197     mSameSize = show;
0198     segmentConnector->setSameSize(show);
0199     compare();
0200 }
0201 
0202 void DiffWidget::slotSegmentsScrollbarHover(int y, double pos)
0203 {
0204     mPreviewWidget->show();
0205     mPreviewWidget->move(mPreviewMargin, qMin(y, widgetSegmentsScrollBar->height() - mPreviewWidgetHeight));
0206 
0207     mPreviewEditorLeft->verticalScrollBar()->setValue(pos * static_cast<double>(mPreviewEditorLeft->verticalScrollBar()->maximum()));
0208     mPreviewEditorRight->verticalScrollBar()->setValue(pos * mPreviewEditorRight->verticalScrollBar()->maximum());
0209 }
0210 
0211 void DiffWidget::slotSplitterSplitterMoved(int, int)
0212 {
0213     recalculateInfoPaneSize();
0214 }
0215 
0216 CodeEditor *DiffWidget::oldCodeEditor() const
0217 {
0218     return leftCodeEditor;
0219 }
0220 
0221 CodeEditor *DiffWidget::newCodeEditor() const
0222 {
0223     return rightCodeEditor;
0224 }
0225 
0226 void DiffWidget::oldCodeEditor_scroll(int value)
0227 {
0228     static bool b{false};
0229     if (b)
0230         return;
0231     b = true;
0232     rightCodeEditor->verticalScrollBar()->setValue(
0233         (int)(((float)value / (float)rightCodeEditor->verticalScrollBar()->maximum()) * (float)rightCodeEditor->verticalScrollBar()->maximum()));
0234     b = false;
0235     segmentConnector->update();
0236     widgetSegmentsScrollBar->update();
0237 }
0238 
0239 void DiffWidget::newCodeEditor_scroll(int value)
0240 {
0241     static bool b{false};
0242     if (b)
0243         return;
0244     b = true;
0245     leftCodeEditor->verticalScrollBar()->setValue(
0246         (int)(((float)value / (float)leftCodeEditor->verticalScrollBar()->maximum()) * (float)leftCodeEditor->verticalScrollBar()->maximum()));
0247     b = false;
0248     segmentConnector->update();
0249     widgetSegmentsScrollBar->update();
0250 }
0251 
0252 void DiffWidget::oldCodeEditor_blockSelected()
0253 {
0254     //    auto b = _oldCodeEditor->textCursor().block().blockNumber();
0255     //    auto b = _oldCodeEditor->currentSegment();
0256     //    if (b) {
0257     //        _segmentConnector->setCurrentSegment(b);
0258     //        _newCodeEditor->highlightSegment(b);
0259     //    }
0260 }
0261 
0262 void DiffWidget::newCodeEditor_blockSelected()
0263 {
0264     //    auto b = _newCodeEditor->currentSegment();
0265     //    if (b) {
0266     //        _segmentConnector->setCurrentSegment(b);
0267     //        _oldCodeEditor->highlightSegment(b);
0268     //    }
0269 }
0270 
0271 void DiffWidget::recalculateInfoPaneSize()
0272 {
0273     //    leftInfoContainer->setMinimumWidth(leftCodeEditor->width());
0274     //    rightInfoContainer->setMinimumWidth(rightCodeEditor->width());
0275 
0276     //    leftInfoContainer->setVisible(leftCodeEditor->width());
0277     //    rightInfoContainer->setVisible(rightCodeEditor->width());
0278 
0279     //    label->setMinimumWidth(leftCodeEditor->width());
0280     //    label_2->setMinimumWidth(rightCodeEditor->width());
0281 }
0282 
0283 void DiffWidget::resizeEvent(QResizeEvent *event)
0284 {
0285     QWidget::resizeEvent(event);
0286     recalculateInfoPaneSize();
0287     mPreviewWidget->resize(splitter->width(), mPreviewWidgetHeight);
0288     mPreviewMargin = splitter->mapToParent(QPoint{0, 0}).x();
0289 }
0290 
0291 void DiffWidget::showEvent(QShowEvent *event)
0292 {
0293     Q_UNUSED(event)
0294     recalculateInfoPaneSize();
0295 }
0296 
0297 bool DiffWidget::sameSize() const
0298 {
0299     return mSameSize;
0300 }
0301 
0302 void DiffWidget::setSameSize(bool newSameSize)
0303 {
0304     if (mSameSize == newSameSize)
0305         return;
0306     mSameSize = newSameSize;
0307     Q_EMIT sameSizeChanged();
0308 }
0309 
0310 void DiffWidget::scrollToTop()
0311 {
0312     leftCodeEditor->setTextCursor(QTextCursor(leftCodeEditor->document()->findBlockByNumber(0)));
0313     rightCodeEditor->setTextCursor(QTextCursor(rightCodeEditor->document()->findBlockByNumber(0)));
0314     //    leftCodeEditor->verticalScrollBar()->setValue(0);
0315     //    rightCodeEditor->verticalScrollBar()->setValue(0);
0316     segmentConnector->update();
0317 }
0318 
0319 #include "moc_diffwidget.cpp"