2 Author: Marco Costalba (C) 2005-2007
4 Copyright: See COPYING file that comes with this distribution
8 #include <QTextCharFormat>
12 #include "myprocess.h"
13 #include "patchcontent.h"
15 void DiffHighlighter::highlightBlock(const QString
& text
) {
17 // state is used to count paragraphs, starting from 0
18 setCurrentBlockState(previousBlockState() + 1);
22 const bool useDark
= QPalette().color(QPalette::Window
).value() > QPalette().color(QPalette::WindowText
).value();
24 QBrush blue
= useDark
? Qt::darkBlue
: QColor(Qt::cyan
);
25 QBrush green
= useDark
? Qt::darkGreen
: QColor(Qt::green
);
26 QBrush magenta
= useDark
? Qt::darkMagenta
: QColor(Qt::magenta
);
27 QBrush backgroundPurple
= useDark
? QGit::PURPLE
: QGit::PURPLE
.darker(600);
29 QTextCharFormat myFormat
;
30 const char firstChar
= text
.at(0).toLatin1();
33 myFormat
.setForeground(magenta
);
36 myFormat
.setForeground(green
);
39 myFormat
.setForeground(Qt::red
);
48 if (text
.startsWith("diff --git a/")) {
49 myFormat
.setForeground(blue
);
50 myFormat
.setBackground(backgroundPurple
);
51 } else if (text
.startsWith("copy ")
52 || text
.startsWith("index ")
53 || text
.startsWith("new ")
54 || text
.startsWith("old ")
55 || text
.startsWith("rename ")
56 || text
.startsWith("similarity "))
57 myFormat
.setForeground(blue
);
59 else if (cl
> 0 && text
.startsWith("diff --combined")) {
60 myFormat
.setForeground(blue
);
61 myFormat
.setBackground(backgroundPurple
);
66 if (text
.left(cl
).contains('+'))
67 myFormat
.setForeground(green
);
68 else if (text
.left(cl
).contains('-'))
69 myFormat
.setForeground(Qt::red
);
73 if (myFormat
.isValid())
74 setFormat(0, text
.length(), myFormat
);
76 PatchContent
* pc
= static_cast<PatchContent
*>(parent());
77 if (pc
->matches
.count() > 0) {
78 int indexFrom
, indexTo
;
79 if (pc
->getMatch(currentBlockState(), &indexFrom
, &indexTo
)) {
81 QTextEdit
* te
= dynamic_cast<QTextEdit
*>(parent());
83 fmt
.setFont(te
->currentFont());
84 fmt
.setFontWeight(QFont::Bold
);
85 fmt
.setForeground(Qt::blue
);
87 indexTo
= text
.length();
89 setFormat(indexFrom
, indexTo
- indexFrom
, fmt
);
94 PatchContent::PatchContent(QWidget
* parent
) : QTextEdit(parent
) {
96 diffLoaded
= seekTarget
= false;
97 curFilter
= prevFilter
= VIEW_ALL
;
99 pickAxeRE
.setMinimal(true);
100 pickAxeRE
.setCaseSensitivity(Qt::CaseInsensitive
);
102 setFont(QGit::TYPE_WRITER_FONT
);
103 diffHighlighter
= new DiffHighlighter(this);
106 void PatchContent::setup(Domain
*, Git
* g
) {
111 void PatchContent::clear() {
113 git
->cancelProcess(proc
);
115 patchRowData
.clear();
119 seekTarget
= !target
.isEmpty();
122 void PatchContent::refresh() {
124 int topPara
= topToLineNum();
125 setUpdatesEnabled(false);
126 QByteArray
tmp(patchRowData
);
129 processData(patchRowData
, &topPara
);
130 scrollLineToTop(topPara
);
131 setUpdatesEnabled(true);
134 void PatchContent::scrollCursorToTop() {
136 QRect r
= cursorRect();
137 QScrollBar
* vsb
= verticalScrollBar();
138 vsb
->setValue(vsb
->value() + r
.top());
141 void PatchContent::scrollLineToTop(int lineNum
) {
143 QTextCursor tc
= textCursor();
144 tc
.movePosition(QTextCursor::Start
);
145 tc
.movePosition(QTextCursor::NextBlock
, QTextCursor::MoveAnchor
, lineNum
);
150 int PatchContent::positionToLineNum(int pos
) {
152 QTextCursor tc
= textCursor();
154 return tc
.blockNumber();
157 int PatchContent::topToLineNum() {
159 return cursorForPosition(QPoint(1, 1)).blockNumber();
162 bool PatchContent::centerTarget(SCRef target
) {
164 moveCursor(QTextCursor::Start
);
166 // find() updates cursor position
167 if (!find(target
, QTextDocument::FindCaseSensitively
| QTextDocument::FindWholeWords
))
170 // move to the beginning of the line
171 moveCursor(QTextCursor::StartOfLine
);
173 // grap copy of current cursor state
174 QTextCursor tc
= textCursor();
176 // move the target line to the top
177 moveCursor(QTextCursor::End
);
183 void PatchContent::centerOnFileHeader(StateInfo
& st
) {
185 if (st
.fileName().isEmpty())
188 target
= st
.fileName();
189 bool combined
= (st
.isMerge() && !st
.allMergeFiles());
190 git
->formatPatchFileHeader(&target
, st
.sha(), st
.diffToSha(), combined
, st
.allMergeFiles());
191 seekTarget
= !target
.isEmpty();
193 seekTarget
= !centerTarget(target
);
196 void PatchContent::centerMatch(int id
) {
198 if (matches
.count() <= id
)
201 // patchTab->textEditDiff->setSelection(matches[id].paraFrom, matches[id].indexFrom,
202 // matches[id].paraTo, matches[id].indexTo);
205 void PatchContent::procReadyRead(const QByteArray
& data
) {
207 patchRowData
.append(data
);
208 if (document()->isEmpty() && isVisible())
212 void PatchContent::typeWriterFontChanged() {
214 setFont(QGit::TYPE_WRITER_FONT
);
215 setPlainText(toPlainText());
218 void PatchContent::processData(const QByteArray
& fileChunk
, int* prevLineNum
) {
221 if (!QGit::stripPartialParaghraps(fileChunk
, &newLines
, &halfLine
))
224 if (!prevLineNum
&& curFilter
== VIEW_ALL
)
225 goto skip_filter
; // optimize common case
227 { // scoped code because of goto
229 QString filteredLines
;
230 int notNegCnt
= 0, notPosCnt
= 0;
231 QVector
<int> toAdded(1), toRemoved(1); // lines count from 1
233 // prevLineNum will be set to the number of corresponding
234 // line in full patch. Number is negative just for algorithm
235 // reasons, prevLineNum counts lines from 1
236 if (prevLineNum
&& prevFilter
== VIEW_ALL
)
237 *prevLineNum
= -(*prevLineNum
); // set once
239 const QStringList
sl(newLines
.split('\n', QString::KeepEmptyParts
));
240 FOREACH_SL (it
, sl
) {
242 // do not remove diff header because of centerTarget
243 bool n
= (*it
).startsWith('-') && !(*it
).startsWith("---");
244 bool p
= (*it
).startsWith('+') && !(*it
).startsWith("+++");
251 toAdded
.append(notNegCnt
);
252 toRemoved
.append(notPosCnt
);
254 int curLineNum
= toAdded
.count() - 1;
256 bool toRemove
= (n
&& curFilter
== VIEW_ADDED
) || (p
&& curFilter
== VIEW_REMOVED
);
258 filteredLines
.append(*it
).append('\n');
260 if (prevLineNum
&& *prevLineNum
== notNegCnt
&& prevFilter
== VIEW_ADDED
)
261 *prevLineNum
= -curLineNum
; // set once
263 if (prevLineNum
&& *prevLineNum
== notPosCnt
&& prevFilter
== VIEW_REMOVED
)
264 *prevLineNum
= -curLineNum
; // set once
266 if (prevLineNum
&& *prevLineNum
<= 0) {
267 if (curFilter
== VIEW_ALL
)
268 *prevLineNum
= -(*prevLineNum
);
270 else if (curFilter
== VIEW_ADDED
)
271 *prevLineNum
= toAdded
.at(-(*prevLineNum
));
273 else if (curFilter
== VIEW_REMOVED
)
274 *prevLineNum
= toRemoved
.at(-(*prevLineNum
));
276 if (*prevLineNum
< 0)
279 newLines
= filteredLines
;
281 } // end of scoped code
285 setUpdatesEnabled(false);
287 if (prevLineNum
|| document()->isEmpty()) { // use the faster setPlainText()
289 setPlainText(newLines
);
290 moveCursor(QTextCursor::Start
);
292 int topLine
= cursorForPosition(QPoint(1, 1)).blockNumber();
295 scrollLineToTop(topLine
);
297 QScrollBar
* vsb
= verticalScrollBar();
298 vsb
->setValue(vsb
->value() + cursorRect().top());
299 setUpdatesEnabled(true);
302 void PatchContent::procFinished() {
304 if (!patchRowData
.endsWith("\n"))
305 patchRowData
.append('\n'); // flush pending half lines
307 refresh(); // show patchRowData content
310 seekTarget
= !centerTarget(target
);
313 if (computeMatches()) {
314 diffHighlighter
->rehighlight(); // slow on big data
319 int PatchContent::doSearch(SCRef txt
, int pos
) {
322 return txt
.indexOf(pickAxeRE
, pos
);
324 return txt
.indexOf(pickAxeRE
.pattern(), pos
, Qt::CaseInsensitive
);
327 bool PatchContent::computeMatches() {
330 if (pickAxeRE
.isEmpty())
333 SCRef txt
= toPlainText();
334 int pos
, lastPos
= 0, lastPara
= 0;
336 // must be at the end to catch patterns across more the one chunk
337 while ((pos
= doSearch(txt
, lastPos
)) != -1) {
339 matches
.append(MatchSelection());
340 MatchSelection
& s
= matches
.last();
342 s
.paraFrom
= txt
.mid(lastPos
, pos
- lastPos
).count('\n');
343 s
.paraFrom
+= lastPara
;
344 s
.indexFrom
= pos
- txt
.lastIndexOf('\n', pos
) - 1; // index starts from 0
347 pos
+= (isRegExp
? pickAxeRE
.matchedLength() : pickAxeRE
.pattern().length());
350 s
.paraTo
= s
.paraFrom
+ txt
.mid(lastPos
, pos
- lastPos
).count('\n');
351 s
.indexTo
= pos
- txt
.lastIndexOf('\n', pos
) - 1;
352 s
.indexTo
++; // in QTextEdit::setSelection() indexTo is not included
357 return !matches
.isEmpty();
360 bool PatchContent::getMatch(int para
, int* indexFrom
, int* indexTo
) {
362 for (int i
= 0; i
< matches
.count(); i
++)
363 if (matches
[i
].paraFrom
<= para
&& matches
[i
].paraTo
>= para
) {
365 *indexFrom
= (para
== matches
[i
].paraFrom
? matches
[i
].indexFrom
: 0);
366 *indexTo
= (para
== matches
[i
].paraTo
? matches
[i
].indexTo
: 0);
372 void PatchContent::on_highlightPatch(const QString
& exp
, bool re
) {
374 pickAxeRE
.setPattern(exp
);
380 void PatchContent::update(StateInfo
& st
) {
382 bool combined
= (st
.isMerge() && !st
.allMergeFiles());
384 const Rev
* r
= git
->revLookup(st
.sha());
386 diffHighlighter
->setCombinedLength(r
->parentsCount());
388 diffHighlighter
->setCombinedLength(0);
391 proc
= git
->getDiff(st
.sha(), this, st
.diffToSha(), combined
); // non blocking