1#include "FullDiffWidget.h"
2
3#include <CommitInfo.h>
4#include <DiffHelper.h>
5#include <GitCache.h>
6#include <GitHistory.h>
7#include <GitQlientStyles.h>
8
9#include <QLineEdit>
10#include <QPushButton>
11#include <QScrollBar>
12#include <QTextCharFormat>
13#include <QTextCodec>
14#include <QVBoxLayout>
15
16FullDiffWidget::DiffHighlighter::DiffHighlighter(QTextDocument *document)
17 : QSyntaxHighlighter(document)
18{
19}
20
21void FullDiffWidget::DiffHighlighter::highlightBlock(const QString &text)
22{
23 // state is used to count paragraphs, starting from 0
24 setCurrentBlockState(previousBlockState() + 1);
25 if (text.isEmpty())
26 return;
27
28 QTextCharFormat myFormat;
29 const char firstChar = text.at(0).toLatin1();
30 switch (firstChar)
31 {
32 case '@':
33 myFormat.setForeground(GitQlientStyles::getOrange());
34 myFormat.setFontWeight(QFont::ExtraBold);
35 break;
36 case '+':
37 myFormat.setForeground(GitQlientStyles::getGreen());
38 break;
39 case '-':
40 myFormat.setForeground(GitQlientStyles::getRed());
41 break;
42 case 'c':
43 case 'd':
44 case 'i':
45 case 'n':
46 case 'o':
47 case 'r':
48 case 's':
49 if (text.startsWith("diff --git a/"))
50 {
51 myFormat.setForeground(GitQlientStyles::getBlue());
52 myFormat.setFontWeight(QFont::ExtraBold);
53 }
54 else if (text.startsWith("copy ") || text.startsWith("index ") || text.startsWith("new ")
55 || text.startsWith("old ") || text.startsWith("rename ") || text.startsWith("similarity "))
56 myFormat.setForeground(GitQlientStyles::getBlue());
57 break;
58 default:
59 break;
60 }
61 if (myFormat.isValid())
62 setFormat(0, text.length(), myFormat);
63}
64
65FullDiffWidget::FullDiffWidget(const QSharedPointer<GitBase> &git, QSharedPointer<GitCache> cache, QWidget *parent)
66 : IDiffWidget(git, cache, parent)
67 , mGoPrevious(new QPushButton())
68 , mGoNext(new QPushButton())
69 , mDiffWidget(new QPlainTextEdit())
70{
71 setAttribute(Qt::WA_DeleteOnClose);
72
73 diffHighlighter = new DiffHighlighter(mDiffWidget->document());
74
75 QFont font;
76 font.setFamily(QString::fromUtf8("DejaVu Sans Mono"));
77 mDiffWidget->setFont(font);
78 mDiffWidget->setObjectName("textEditDiff");
79 mDiffWidget->setUndoRedoEnabled(false);
80 mDiffWidget->setLineWrapMode(QPlainTextEdit::NoWrap);
81 mDiffWidget->setReadOnly(true);
82 mDiffWidget->setTextInteractionFlags(Qt::TextSelectableByMouse);
83
84 const auto search = new QLineEdit();
85 search->setPlaceholderText(tr("Press Enter to search a text... "));
86 search->setObjectName("SearchInput");
87 connect(search, &QLineEdit::editingFinished, this,
88 [this, search]() { DiffHelper::findString(search->text(), mDiffWidget, this); });
89
90 const auto optionsLayout = new QHBoxLayout();
91 optionsLayout->setContentsMargins(QMargins());
92 optionsLayout->setSpacing(5);
93 optionsLayout->addWidget(mGoPrevious);
94 optionsLayout->addWidget(mGoNext);
95 optionsLayout->addStretch();
96
97 const auto layout = new QVBoxLayout(this);
98 layout->setContentsMargins(10, 10, 10, 10);
99 layout->setSpacing(10);
100 layout->addLayout(optionsLayout);
101 layout->addWidget(search);
102 layout->addWidget(mDiffWidget);
103
104 mGoPrevious->setIcon(QIcon(":/icons/arrow_up"));
105 mGoPrevious->setToolTip(tr("Previous change"));
106 connect(mGoPrevious, &QPushButton::clicked, this, &FullDiffWidget::moveChunkUp);
107
108 mGoNext->setToolTip(tr("Next change"));
109 mGoNext->setIcon(QIcon(":/icons/arrow_down"));
110 connect(mGoNext, &QPushButton::clicked, this, &FullDiffWidget::moveChunkDown);
111}
112
113bool FullDiffWidget::reload()
114{
115 if (mCurrentSha != CommitInfo::ZERO_SHA)
116 {
117 QScopedPointer<GitHistory> git(new GitHistory(mGit));
118 const auto ret = git->getCommitDiff(mCurrentSha, mPreviousSha);
119
120 if (ret.success && !ret.output.isEmpty())
121 {
122 loadDiff(mCurrentSha, mPreviousSha, ret.output);
123 return true;
124 }
125 }
126
127 return false;
128}
129
130void FullDiffWidget::processData(const QString &fileChunk)
131{
132 if (mPreviousDiffText != fileChunk)
133 {
134 mPreviousDiffText = fileChunk;
135
136 auto count = 0;
137 const auto chunks = fileChunk.split("\n");
138
139 for (const auto &chunk : chunks)
140 {
141 if (chunk.startsWith("diff --"))
142 mFilePositions.append(count);
143
144 ++count;
145 }
146
147 const auto pos = mDiffWidget->verticalScrollBar()->value();
148
149 mDiffWidget->setUpdatesEnabled(false);
150 mDiffWidget->clear();
151 mDiffWidget->setPlainText(fileChunk);
152 mDiffWidget->moveCursor(QTextCursor::Start);
153 mDiffWidget->verticalScrollBar()->setValue(pos);
154 mDiffWidget->setUpdatesEnabled(true);
155 }
156}
157
158void FullDiffWidget::moveChunkUp()
159{
160 const auto currentPos = mDiffWidget->verticalScrollBar()->value();
161
162 const auto iter = std::find_if(mFilePositions.crbegin(), mFilePositions.crend(),
163 [currentPos](int pos) { return currentPos > pos; });
164
165 if (iter != mFilePositions.crend())
166 {
167 blockSignals(true);
168 mDiffWidget->verticalScrollBar()->setValue(*iter);
169 blockSignals(false);
170 }
171}
172
173void FullDiffWidget::moveChunkDown()
174{
175 const auto currentPos = mDiffWidget->verticalScrollBar()->value();
176
177 const auto iter = std::find_if(mFilePositions.cbegin(), mFilePositions.cend(),
178 [currentPos](int pos) { return currentPos < pos; });
179
180 if (iter != mFilePositions.cend())
181 {
182 blockSignals(true);
183 mDiffWidget->verticalScrollBar()->setValue(*iter);
184 blockSignals(false);
185 }
186}
187
188void FullDiffWidget::loadDiff(const QString &sha, const QString &diffToSha, const QString &diffData)
189{
190 mCurrentSha = sha;
191 mPreviousSha = diffToSha;
192
193 processData(diffData);
194}
195