Be memory efficient and clear hunk data after displaying it
[vng.git] / hunks / HunksCursor.cpp
blob8c8aaa01025b42fc73aede01abc8c409295f6cad
1 /*
2 * This file is part of the vng project
3 * Copyright (C) 2008 Thomas Zander <tzander@trolltech.com>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "HunksCursor.h"
20 #include "ChangeSet.h"
22 #include <QDebug>
24 HunksCursor::HunksCursor(ChangeSet &changeSet)
25 : m_changeSet(changeSet),
26 m_totalHunks(-1),
27 m_fileIndex(0),
28 m_hunkIndex(0),
29 m_subHunkIndex(0)
31 if (changeSet.count()) {
32 // make sure the first hunk is an actual change
33 File file = changeSet.file(0);
34 if (file.fileName() == file.oldFileName())
35 forward(ItemScope, true);
39 HunksCursor::~HunksCursor()
43 int HunksCursor::forward(Scope scope, bool skipAnswered)
45 if (m_changeSet.count() == 0 || m_fileIndex >= m_changeSet.count())
46 return currentIndex();
48 struct Last {
49 Last(const ChangeSet &changeSet)
50 : hunk(0), subHunk(0)
52 file = changeSet.count() - 1;
53 if (! changeSet.hasAllHunks())
54 return;
55 File f = changeSet.file(file);
56 hunk = f.count() - 1;
57 if (hunk >= 0) {
58 Hunk h = f.hunks()[hunk];
59 hunk += 2;
60 subHunk = h.subHunkCount() - 1;
63 int file, hunk, subHunk;
65 Last last(m_changeSet);
66 if (m_fileIndex > last.file) {
67 m_fileIndex = last.file +1;
68 m_hunkIndex = 0;
69 m_subHunkIndex = 0;
70 return currentIndex(); // already at end.
73 switch(scope) {
74 case ItemScope: {
75 if (++m_subHunkIndex < currentSubHunkCount())
76 break;
78 --m_subHunkIndex;
79 File file = m_changeSet.file(m_fileIndex);
80 if (m_hunkIndex + 1 - 2 >= file.count())
81 return forward(BlockScope, skipAnswered);
82 else {
83 m_hunkIndex++;
84 m_subHunkIndex = 0;
86 break;
88 case BlockScope:
89 m_fileIndex++;
90 m_hunkIndex = 0;
91 m_subHunkIndex = 0;
92 if (m_fileIndex > last.file)
93 skipAnswered = false; // there is nothing left;
94 break;
95 case FullRange:
96 m_fileIndex = last.file + 1;
97 m_hunkIndex = 0;
98 m_subHunkIndex = 0;
99 break;
102 if (! isValid())
103 return currentIndex();
105 File file = m_changeSet.file(m_fileIndex);
106 const bool renamed = file.fileName() != file.oldFileName();
107 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
108 if (m_hunkIndex == 0 && !renamed)
109 m_hunkIndex++;
110 if (m_hunkIndex == 1 && !protectionChanged)
111 return forward(ItemScope, skipAnswered);
113 if (skipAnswered) {
114 if (m_hunkIndex == 0 && file.renameAcceptance() != Vng::Undecided
115 || m_hunkIndex == 1 && file.protectionAcceptance() != Vng::Undecided)
116 return forward(ItemScope, skipAnswered);
118 if (m_hunkIndex >= 2) { // a hunk;
119 Hunk hunk = file.hunks()[m_hunkIndex - 2];
120 Vng::Acceptance a = hunk.acceptance();
121 if (a == Vng::Accepted || a == Vng::Rejected || hunk.acceptance(m_subHunkIndex) != Vng::Undecided) {
122 if (hunk.subHunkCount() == m_subHunkIndex + 1)
123 return forward(BlockScope, skipAnswered);
124 return forward(ItemScope, skipAnswered);
128 return currentIndex();
131 int HunksCursor::back(Scope scope)
133 if (m_changeSet.count() == 0
134 || m_fileIndex == 0 && m_hunkIndex == 0 && m_subHunkIndex == 0)
135 return currentIndex();
136 switch(scope) {
137 case ItemScope:
138 if (--m_subHunkIndex >= 0)
139 break;
140 ++m_subHunkIndex;
141 if (--m_hunkIndex < 1) {
142 m_hunkIndex++;
143 return back(BlockScope);
145 m_subHunkIndex = currentSubHunkCount() - 1;
146 break;
147 case BlockScope:
148 if (--m_fileIndex < 0)
149 m_fileIndex = 0;
150 m_hunkIndex = m_changeSet.file(m_fileIndex).count() -1+2;
151 Q_ASSERT(m_hunkIndex >= 0);
152 m_subHunkIndex = currentSubHunkCount() - 1;
153 break;
154 case FullRange:
155 m_hunkIndex = 0;
156 m_fileIndex = 0;
157 m_subHunkIndex = 0;
158 break;
161 File file = m_changeSet.file(m_fileIndex);
162 const bool renamed = file.fileName() != file.oldFileName();
163 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
164 if (m_hunkIndex == 1 && !protectionChanged)
165 m_hunkIndex--;
166 if (m_hunkIndex == 0 && !renamed)
167 return back(BlockScope);
169 return currentIndex();
172 void HunksCursor::setResponse(bool response, Scope scope)
174 if (m_changeSet.count() == 0)
175 return;
176 const Vng::Acceptance accept = response ? Vng::Accepted : Vng::Rejected;
178 if (scope == ItemScope) {
179 File file = m_changeSet.file(m_fileIndex);
180 if (m_hunkIndex == 0) {
181 if (!response && file.oldFileName().isEmpty()
182 || response && file.fileName().isEmpty()) { // new file that is not added, removed file that we want added.
183 setResponse(response, BlockScope);
184 return;
186 file.setRenameAcceptance(accept);
188 else if (m_hunkIndex == 1)
189 file.setProtectionAcceptance(accept);
190 else {
191 Hunk hunk = file.hunks()[m_hunkIndex - 2];
192 hunk.setAcceptance(m_subHunkIndex, accept);
194 return;
197 int fileIndex = m_fileIndex;
198 int hunkIndex = m_hunkIndex;
200 do {
201 File file = m_changeSet.file(fileIndex);
202 QList<Hunk> hunks = file.hunks();
203 do {
204 if (hunkIndex == 0)
205 file.setRenameAcceptance(accept);
206 else if (hunkIndex == 1)
207 file.setProtectionAcceptance(accept);
208 else
209 hunks[hunkIndex - 2].setAcceptance(accept);
210 hunkIndex++;
211 } while(scope >= BlockScope && hunkIndex -2 < hunks.count());
212 hunkIndex = 0;
213 fileIndex++;
214 } while(scope == FullRange && fileIndex < m_changeSet.count());
217 int HunksCursor::count()
219 if (m_totalHunks == -1 && m_changeSet.hasAllHunks())
220 forceCount();
221 return m_totalHunks;
224 void HunksCursor::forceCount()
226 if (m_totalHunks < 0) {
227 int total = 0;
228 for (int i=0; i < m_changeSet.count(); ++i) {
229 File file = m_changeSet.file(i);
230 if (file.fileName() != file.oldFileName())
231 total++;
232 else if (file.protection() != file.oldProtection())
233 total++;
234 foreach(Hunk hunk, file.hunks())
235 total += hunk.subHunkCount();
237 m_totalHunks = total;
241 int HunksCursor::currentIndex() const
243 int answer = 1;
244 const int end = qMin(m_changeSet.count(), m_fileIndex+1);
245 int i=0;
246 while(i < end) {
247 File file = m_changeSet.file(i);
248 const bool renamed = file.fileName() != file.oldFileName();
249 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
250 QList<Hunk> hunks = file.hunks();
251 ++i;
252 if (i == end && m_fileIndex < m_changeSet.count()) {
253 if (m_hunkIndex > 0 && renamed)
254 answer++;
255 if (m_hunkIndex > 1 && protectionChanged)
256 answer++;
257 hunks = hunks.mid(0, m_hunkIndex - 2);
259 else {
260 if (renamed)
261 answer++;
262 if (protectionChanged)
263 answer++;
265 QList<Hunk>::Iterator iter2 = hunks.begin();
266 int index = 2;
267 while(iter2 != hunks.end()) {
268 if (isValid() && i == end && m_hunkIndex < index++)
269 break;
270 answer += (*iter2).subHunkCount();
271 ++iter2;
274 return answer + m_subHunkIndex;
277 QString HunksCursor::currentText() const
279 QByteArray bytes;
280 if (m_changeSet.count() <= m_fileIndex)
281 return QString();
282 File file = m_changeSet.file(m_fileIndex);
284 QString output;
285 QTextStream ts(&output);
286 if (m_config)
287 m_config->colorize(ts);
288 if (m_hunkIndex == 0) { // file rename
289 if (file.fileName().isEmpty())
290 ts << "remove ";
291 else if (file.oldFileName().isEmpty())
292 ts << "add ";
293 else
294 ts << "move ";
296 else if (m_hunkIndex == 1) // protections change
297 ts << "mode change ";
298 else
299 ts << "hunk ";
300 if (m_config)
301 m_config->normalColor(ts);
302 if (m_hunkIndex == 0) {
303 if (!file.oldFileName().isEmpty())
304 ts << "`" << file.oldFileName() << "' ";
305 if (! file.fileName().isEmpty())
306 ts << "`" << file.fileName() << "'";
308 else if (m_hunkIndex == 1)
309 ts << file.fileName() << " " << file.oldProtection() << " => " << file.protection() << endl;
311 if (m_hunkIndex < 2) {
312 ts << endl;
313 ts.flush();
314 return output;
317 if (file.count() <= m_hunkIndex - 2)
318 return "no change\n";
319 Hunk hunk = file.hunks()[m_hunkIndex - 2];
320 bytes.append(file.fileName());
321 bytes.append(" ");
322 bytes.append(QString::number(hunk.lineNumber(m_subHunkIndex)));
323 //bytes.append("[subhunk "+ QString::number(m_subHunkIndex + 1) +"/"+ QString::number(hunk.subHunkCount()) +"]");
324 bytes.append("\n");
326 QByteArray patch = hunk.subHunk(m_subHunkIndex);
327 //m_config.normalColor(out);
328 if (patch.contains((char) 0)) { // binary
329 //m_config.colorize(out);
330 bytes.append(QString("binary data\n"));
331 //m_config.normalColor(out);
333 else
334 bytes.append(patch);
336 ts.flush();
337 return output + QString(bytes);
340 QString HunksCursor::helpMessage() const
342 return QString(
343 "How to use revert...\n"
344 "y: revert this patch\n"
345 "n: don't revert it\n\n"
347 "s: don't revert the rest of the changes to this file\n"
348 "f: revert the rest of the changes to this file\n\n"
350 "d: revert selected patches, skipping all the remaining patches\n"
351 "a: revert all the remaining patches\n"
352 "q: cancel revert\n\n"
354 "j: skip to next patch\n"
355 "k: back up to previous patch\n"
356 "c: calculate number of patches\n"
357 "h or ?: show this help\n");
360 int HunksCursor::currentSubHunkCount()
362 File file = m_changeSet.file(m_fileIndex);
363 if (m_hunkIndex < 2)
364 return 1;
365 Hunk hunk = file.hunks()[m_hunkIndex - 2];
366 return hunk.subHunkCount();
369 bool HunksCursor::isValid() const
371 return m_changeSet.count() > 0 && m_changeSet.count() > m_fileIndex;
374 QString HunksCursor::allowedOptions() const
376 QString allowed = "ynsfqadjk";
377 if (m_totalHunks == -1)
378 allowed += "c";
379 return allowed;