Make the cursor movements over commits work better, unrecord still is not fully done...
[vng.git] / hunks / HunksCursor.cpp
blob5c920fe7584f7ee68f14d2f4c8c9bb15fec8884a
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"
21 #include <QDebug>
23 HunksCursor::HunksCursor(ChangeSet &changeSet)
24 : m_changeSet(changeSet),
25 m_totalHunks(-1),
26 m_fileIndex(0),
27 m_hunkIndex(0),
28 m_subHunkIndex(0)
30 if (changeSet.count()) {
31 File file = changeSet.files()[0];
32 foreach (File file, changeSet.files()) {
33 if (file.fileName().isEmpty() || file.oldFileName().isEmpty())
34 file.setProtectionAcceptance(Vng::Accepted);
36 // make sure the first hunk is an actual change
37 if (file.fileName() == file.oldFileName())
38 forward(ItemScope, true);
42 HunksCursor::~HunksCursor()
46 int HunksCursor::forward(Scope scope, bool skipAnswered)
48 if (m_changeSet.count() == 0 || m_fileIndex >= m_changeSet.count())
49 return currentIndex();
51 struct Last {
52 Last(const ChangeSet &changeSet)
53 : hunk(0), subHunk(0)
55 file = changeSet.count() - 1;
56 File f = changeSet.files().at(file);
57 hunk = f.count() - 1;
58 if (hunk >= 0) {
59 Hunk h = f.hunks()[hunk];
60 hunk += 2;
61 subHunk = h.subHunkCount() - 1;
64 int file, hunk, subHunk;
66 Last last(m_changeSet);
67 if (m_fileIndex > last.file) {
68 m_fileIndex = last.file +1;
69 m_hunkIndex = 0;
70 m_subHunkIndex = 0;
71 return currentIndex(); // already at end.
74 switch(scope) {
75 case ItemScope: {
76 if (++m_subHunkIndex < currentSubHunkCount())
77 break;
79 --m_subHunkIndex;
80 File file = m_changeSet.files().at(m_fileIndex);
81 if (m_hunkIndex + 1 - 2 >= file.count())
82 return forward(BlockScope, skipAnswered);
83 else {
84 m_hunkIndex++;
85 m_subHunkIndex = 0;
87 break;
89 case BlockScope:
90 m_fileIndex++;
91 m_hunkIndex = 0;
92 m_subHunkIndex = 0;
93 if (m_fileIndex > last.file)
94 skipAnswered = false; // there is nothing left;
95 break;
96 case FullRange:
97 m_fileIndex = last.file + 1;
98 m_hunkIndex = 0;
99 m_subHunkIndex = 0;
100 break;
103 if (! isValid())
104 return currentIndex();
106 File file = m_changeSet.files()[m_fileIndex];
107 const bool renamed = file.fileName() != file.oldFileName();
108 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
109 if (m_hunkIndex == 0 && !renamed)
110 m_hunkIndex++;
111 if (m_hunkIndex == 1 && !protectionChanged)
112 return forward(ItemScope, skipAnswered);
114 if (skipAnswered) {
115 if (m_hunkIndex == 0 && file.renameAcceptance() != Vng::Undecided
116 || m_hunkIndex == 1 && file.protectionAcceptance() != Vng::Undecided)
117 return forward(ItemScope, skipAnswered);
119 if (m_hunkIndex >= 2) { // a hunk;
120 Hunk hunk = file.hunks()[m_hunkIndex - 2];
121 Vng::Acceptance a = hunk.acceptance();
122 if (a == Vng::Accepted || a == Vng::Rejected || hunk.acceptance(m_subHunkIndex) != Vng::Undecided) {
123 if (hunk.subHunkCount() == m_subHunkIndex + 1)
124 return forward(BlockScope, skipAnswered);
125 return forward(ItemScope, skipAnswered);
129 return currentIndex();
132 int HunksCursor::back(Scope scope)
134 if (m_changeSet.count() == 0
135 || m_fileIndex == 0 && m_hunkIndex == 0 && m_subHunkIndex == 0)
136 return currentIndex();
137 switch(scope) {
138 case ItemScope:
139 if (--m_subHunkIndex >= 0)
140 break;
141 ++m_subHunkIndex;
142 if (--m_hunkIndex < 1) {
143 m_hunkIndex++;
144 return back(BlockScope);
146 m_subHunkIndex = currentSubHunkCount() - 1;
147 break;
148 case BlockScope:
149 if (--m_fileIndex < 0)
150 m_fileIndex = 0;
151 m_hunkIndex = m_changeSet.files().at(m_fileIndex).count() -1+2;
152 Q_ASSERT(m_hunkIndex >= 0);
153 m_subHunkIndex = currentSubHunkCount() - 1;
154 break;
155 case FullRange:
156 m_hunkIndex = 0;
157 m_fileIndex = 0;
158 m_subHunkIndex = 0;
159 break;
162 File file = m_changeSet.files()[m_fileIndex];
163 const bool renamed = file.fileName() != file.oldFileName();
164 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
165 if (m_hunkIndex == 1 && !protectionChanged)
166 m_hunkIndex--;
167 if (m_hunkIndex == 0 && !renamed)
168 return back(BlockScope);
170 return currentIndex();
173 void HunksCursor::setResponse(bool response, Scope scope)
175 if (m_changeSet.count() == 0)
176 return;
177 const Vng::Acceptance accept = response ? Vng::Accepted : Vng::Rejected;
179 if (scope == ItemScope) {
180 File file = m_changeSet.files()[m_fileIndex];
181 if (m_hunkIndex == 0)
182 file.setRenameAcceptance(accept);
183 else if (m_hunkIndex == 1)
184 file.setProtectionAcceptance(accept);
185 else {
186 Hunk hunk = file.hunks()[m_hunkIndex - 2];
187 hunk.setAcceptance(m_subHunkIndex, accept);
189 return;
192 int fileIndex = m_fileIndex;
193 int hunkIndex = m_hunkIndex;
195 do {
196 File file = m_changeSet.files()[fileIndex];
197 QList<Hunk> hunks = file.hunks();
198 do {
199 if (hunkIndex == 0)
200 file.setRenameAcceptance(accept);
201 else if (hunkIndex == 1)
202 file.setProtectionAcceptance(accept);
203 else
204 hunks[hunkIndex - 2].setAcceptance(accept);
205 hunkIndex++;
206 } while(scope >= BlockScope && hunkIndex -2 < hunks.count());
207 hunkIndex = 0;
208 fileIndex++;
209 } while(scope == FullRange && fileIndex < m_changeSet.count());
212 int HunksCursor::count()
214 return m_totalHunks;
217 void HunksCursor::forceCount()
219 if (m_totalHunks < 0) {
220 int total = 0;
221 foreach(File file, m_changeSet.files()) {
222 if (file.fileName() != file.oldFileName())
223 total++;
224 else if (file.protection() != file.oldProtection())
225 total++;
226 foreach(Hunk hunk, file.hunks())
227 total += hunk.subHunkCount();
229 m_totalHunks = total;
233 int HunksCursor::currentIndex() const
235 int answer = 1;
236 QList<File> files = m_changeSet.files().mid(0, m_fileIndex + 1);
237 QList<File>::Iterator iter = files.begin();
238 while(iter != files.end()) {
239 File file = *iter;
240 const bool renamed = file.fileName() != file.oldFileName();
241 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
242 QList<Hunk> hunks = file.hunks();
243 ++iter;
244 if (iter == files.end() && m_fileIndex < m_changeSet.count()) {
245 if (m_hunkIndex > 0 && renamed)
246 answer++;
247 if (m_hunkIndex > 1 && protectionChanged)
248 answer++;
249 hunks = hunks.mid(0, m_hunkIndex - 2);
251 else {
252 if (renamed)
253 answer++;
254 if (protectionChanged)
255 answer++;
257 QList<Hunk>::Iterator iter2 = hunks.begin();
258 int index = 2;
259 while(iter2 != hunks.end()) {
260 if (isValid() && iter == files.end() && m_hunkIndex < index++)
261 break;
262 answer += (*iter2).subHunkCount();
263 ++iter2;
266 return answer + m_subHunkIndex;
269 QString HunksCursor::currentText() const
271 QByteArray bytes;
272 if (m_changeSet.count() <= m_fileIndex)
273 return QString();
274 File file = m_changeSet.files()[m_fileIndex];
276 QString output;
277 QTextStream ts(&output);
278 if (m_config)
279 m_config->colorize(ts);
280 if (m_hunkIndex == 0) { // file rename
281 if (file.fileName().isEmpty())
282 ts << "remove ";
283 else if (file.oldFileName().isEmpty())
284 ts << "add ";
285 else
286 ts << "move ";
288 else if (m_hunkIndex == 1) // protections change
289 ts << "mode change ";
290 else
291 ts << "hunk ";
292 if (m_config)
293 m_config->normalColor(ts);
294 if (m_hunkIndex == 0) {
295 if (!file.oldFileName().isEmpty())
296 ts << "`" << file.oldFileName() << "' ";
297 if (! file.fileName().isEmpty())
298 ts << "`" << file.fileName() << "'";
300 else if (m_hunkIndex == 1)
301 ts << file.fileName() << " " << file.oldProtection() << " => " << file.protection() << endl;
303 if (m_hunkIndex < 2) {
304 ts << endl;
305 ts.flush();
306 return output;
309 if (file.count() <= m_hunkIndex - 2)
310 return "no change\n";
311 Hunk hunk = file.hunks()[m_hunkIndex - 2];
312 bytes.append(file.fileName());
313 bytes.append(" ");
314 bytes.append(QString::number(hunk.lineNumber(m_subHunkIndex)));
315 //bytes.append("[subhunk "+ QString::number(m_subHunkIndex + 1) +"/"+ QString::number(hunk.subHunkCount()) +"]");
316 bytes.append("\n");
318 QByteArray patch = hunk.subHunk(m_subHunkIndex);
319 //m_config.normalColor(out);
320 if (patch.contains((char) 0)) { // binary
321 //m_config.colorize(out);
322 bytes.append(QString("binary data\n"));
323 //m_config.normalColor(out);
325 else
326 bytes.append(patch);
328 ts.flush();
329 return output + QString(bytes);
332 QString HunksCursor::helpMessage() const
334 return QString(
335 "How to use revert...\n"
336 "y: revert this patch\n"
337 "n: don't revert it\n\n"
339 "s: don't revert the rest of the changes to this file\n"
340 "f: revert the rest of the changes to this file\n\n"
342 "d: revert selected patches, skipping all the remaining patches\n"
343 "a: revert all the remaining patches\n"
344 "q: cancel revert\n\n"
346 "j: skip to next patch\n"
347 "k: back up to previous patch\n"
348 "c: calculate number of patches\n"
349 "h or ?: show this help\n");
352 int HunksCursor::currentSubHunkCount()
354 File file = m_changeSet.files()[m_fileIndex];
355 if (m_hunkIndex < 2)
356 return 1;
357 Hunk hunk = file.hunks()[m_hunkIndex - 2];
358 return hunk.subHunkCount();
361 bool HunksCursor::isValid() const
363 return m_changeSet.count() > 0 && m_changeSet.count() > m_fileIndex;
366 QString HunksCursor::allowedOptions() const
368 QString allowed = "ynsfqadjk";
369 if (m_totalHunks == -1)
370 allowed += "c";
371 return allowed;