Version 2.6
[qgit4/redivivus.git] / src / patchcontent.cpp
blob43f883f210ae1809647a552e17494bfabcc093a0
1 /*
2 Author: Marco Costalba (C) 2005-2007
4 Copyright: See COPYING file that comes with this distribution
6 */
7 #include <QScrollBar>
8 #include <QTextCharFormat>
9 #include "common.h"
10 #include "domain.h"
11 #include "git.h"
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);
19 if (text.isEmpty())
20 return;
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();
31 switch (firstChar) {
32 case '@':
33 myFormat.setForeground(magenta);
34 break;
35 case '+':
36 myFormat.setForeground(green);
37 break;
38 case '-':
39 myFormat.setForeground(Qt::red);
40 break;
41 case 'c':
42 case 'd':
43 case 'i':
44 case 'n':
45 case 'o':
46 case 'r':
47 case 's':
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);
63 break;
64 case ' ':
65 if (cl > 0) {
66 if (text.left(cl).contains('+'))
67 myFormat.setForeground(green);
68 else if (text.left(cl).contains('-'))
69 myFormat.setForeground(Qt::red);
71 break;
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());
82 QTextCharFormat fmt;
83 fmt.setFont(te->currentFont());
84 fmt.setFontWeight(QFont::Bold);
85 fmt.setForeground(Qt::blue);
86 if (indexTo == 0)
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) {
108 git = g;
111 void PatchContent::clear() {
113 git->cancelProcess(proc);
114 QTextEdit::clear();
115 patchRowData.clear();
116 halfLine = "";
117 matches.clear();
118 diffLoaded = false;
119 seekTarget = !target.isEmpty();
122 void PatchContent::refresh() {
124 int topPara = topToLineNum();
125 setUpdatesEnabled(false);
126 QByteArray tmp(patchRowData);
127 clear();
128 patchRowData = tmp;
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);
146 setTextCursor(tc);
147 scrollCursorToTop();
150 int PatchContent::positionToLineNum(int pos) {
152 QTextCursor tc = textCursor();
153 tc.setPosition(pos);
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))
168 return false;
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);
178 setTextCursor(tc);
180 return true;
183 void PatchContent::centerOnFileHeader(StateInfo& st) {
185 if (st.fileName().isEmpty())
186 return;
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();
192 if (seekTarget)
193 seekTarget = !centerTarget(target);
196 void PatchContent::centerMatch(int id) {
198 if (matches.count() <= id)
199 return;
200 //FIXME
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())
209 processData(data);
212 void PatchContent::typeWriterFontChanged() {
214 setFont(QGit::TYPE_WRITER_FONT);
215 setPlainText(toPlainText());
218 void PatchContent::processData(const QByteArray& fileChunk, int* prevLineNum) {
220 QString newLines;
221 if (!QGit::stripPartialParaghraps(fileChunk, &newLines, &halfLine))
222 return;
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("+++");
246 if (!p)
247 notPosCnt++;
248 if (!n)
249 notNegCnt++;
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);
257 if (!toRemove)
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)
277 *prevLineNum = 0;
279 newLines = filteredLines;
281 } // end of scoped code
283 skip_filter:
285 setUpdatesEnabled(false);
287 if (prevLineNum || document()->isEmpty()) { // use the faster setPlainText()
289 setPlainText(newLines);
290 moveCursor(QTextCursor::Start);
291 } else {
292 int topLine = cursorForPosition(QPoint(1, 1)).blockNumber();
293 append(newLines);
294 if (topLine > 0)
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
309 if (seekTarget)
310 seekTarget = !centerTarget(target);
312 diffLoaded = true;
313 if (computeMatches()) {
314 diffHighlighter->rehighlight(); // slow on big data
315 centerMatch();
319 int PatchContent::doSearch(SCRef txt, int pos) {
321 if (isRegExp)
322 return txt.indexOf(pickAxeRE, pos);
324 return txt.indexOf(pickAxeRE.pattern(), pos, Qt::CaseInsensitive);
327 bool PatchContent::computeMatches() {
329 matches.clear();
330 if (pickAxeRE.isEmpty())
331 return false;
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
346 lastPos = pos;
347 pos += (isRegExp ? pickAxeRE.matchedLength() : pickAxeRE.pattern().length());
348 pos--;
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
354 lastPos = pos;
355 lastPara = s.paraTo;
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);
367 return true;
369 return false;
372 void PatchContent::on_highlightPatch(const QString& exp, bool re) {
374 pickAxeRE.setPattern(exp);
375 isRegExp = re;
376 if (diffLoaded)
377 procFinished();
380 void PatchContent::update(StateInfo& st) {
382 bool combined = (st.isMerge() && !st.allMergeFiles());
383 if (combined) {
384 const Rev* r = git->revLookup(st.sha());
385 if (r)
386 diffHighlighter->setCombinedLength(r->parentsCount());
387 } else
388 diffHighlighter->setCombinedLength(0);
390 clear();
391 proc = git->getDiff(st.sha(), this, st.diffToSha(), combined); // non blocking