Finish up the matcher for basic .gitignore matching
[vng.git] / hunks / HunksCursor.cpp
blob28046c4146b68130d5b55a6923931281de2f0554
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 File file = changeSet.files()[0];
33 foreach (File file, changeSet.files()) {
34 if (file.fileName().isEmpty() || file.oldFileName().isEmpty())
35 file.setProtectionAcceptance(Vng::Accepted);
37 // make sure the first hunk is an actual change
38 if (file.fileName() == file.oldFileName())
39 forward(ItemScope, true);
43 HunksCursor::~HunksCursor()
47 int HunksCursor::forward(Scope scope, bool skipAnswered)
49 if (m_changeSet.count() == 0 || m_fileIndex >= m_changeSet.count())
50 return currentIndex();
52 struct Last {
53 Last(const ChangeSet &changeSet)
54 : hunk(0), subHunk(0)
56 file = changeSet.count() - 1;
57 File f = changeSet.files().at(file);
58 hunk = f.count() - 1;
59 if (hunk >= 0) {
60 Hunk h = f.hunks()[hunk];
61 hunk += 2;
62 subHunk = h.subHunkCount() - 1;
65 int file, hunk, subHunk;
67 Last last(m_changeSet);
68 if (m_fileIndex > last.file) {
69 m_fileIndex = last.file +1;
70 m_hunkIndex = 0;
71 m_subHunkIndex = 0;
72 return currentIndex(); // already at end.
75 switch(scope) {
76 case ItemScope: {
77 if (++m_subHunkIndex < currentSubHunkCount())
78 break;
80 --m_subHunkIndex;
81 File file = m_changeSet.files().at(m_fileIndex);
82 if (m_hunkIndex + 1 - 2 >= file.count())
83 return forward(BlockScope, skipAnswered);
84 else {
85 m_hunkIndex++;
86 m_subHunkIndex = 0;
88 break;
90 case BlockScope:
91 m_fileIndex++;
92 m_hunkIndex = 0;
93 m_subHunkIndex = 0;
94 if (m_fileIndex > last.file)
95 skipAnswered = false; // there is nothing left;
96 break;
97 case FullRange:
98 m_fileIndex = last.file + 1;
99 m_hunkIndex = 0;
100 m_subHunkIndex = 0;
101 break;
104 if (! isValid())
105 return currentIndex();
107 File file = m_changeSet.files()[m_fileIndex];
108 const bool renamed = file.fileName() != file.oldFileName();
109 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
110 if (m_hunkIndex == 0 && !renamed)
111 m_hunkIndex++;
112 if (m_hunkIndex == 1 && !protectionChanged)
113 return forward(ItemScope, skipAnswered);
115 if (skipAnswered) {
116 if (m_hunkIndex == 0 && file.renameAcceptance() != Vng::Undecided
117 || m_hunkIndex == 1 && file.protectionAcceptance() != Vng::Undecided)
118 return forward(ItemScope, skipAnswered);
120 if (m_hunkIndex >= 2) { // a hunk;
121 Hunk hunk = file.hunks()[m_hunkIndex - 2];
122 Vng::Acceptance a = hunk.acceptance();
123 if (a == Vng::Accepted || a == Vng::Rejected || hunk.acceptance(m_subHunkIndex) != Vng::Undecided) {
124 if (hunk.subHunkCount() == m_subHunkIndex + 1)
125 return forward(BlockScope, skipAnswered);
126 return forward(ItemScope, skipAnswered);
130 return currentIndex();
133 int HunksCursor::back(Scope scope)
135 if (m_changeSet.count() == 0
136 || m_fileIndex == 0 && m_hunkIndex == 0 && m_subHunkIndex == 0)
137 return currentIndex();
138 switch(scope) {
139 case ItemScope:
140 if (--m_subHunkIndex >= 0)
141 break;
142 ++m_subHunkIndex;
143 if (--m_hunkIndex < 1) {
144 m_hunkIndex++;
145 return back(BlockScope);
147 m_subHunkIndex = currentSubHunkCount() - 1;
148 break;
149 case BlockScope:
150 if (--m_fileIndex < 0)
151 m_fileIndex = 0;
152 m_hunkIndex = m_changeSet.files().at(m_fileIndex).count() -1+2;
153 Q_ASSERT(m_hunkIndex >= 0);
154 m_subHunkIndex = currentSubHunkCount() - 1;
155 break;
156 case FullRange:
157 m_hunkIndex = 0;
158 m_fileIndex = 0;
159 m_subHunkIndex = 0;
160 break;
163 File file = m_changeSet.files()[m_fileIndex];
164 const bool renamed = file.fileName() != file.oldFileName();
165 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
166 if (m_hunkIndex == 1 && !protectionChanged)
167 m_hunkIndex--;
168 if (m_hunkIndex == 0 && !renamed)
169 return back(BlockScope);
171 return currentIndex();
174 void HunksCursor::setResponse(bool response, Scope scope)
176 if (m_changeSet.count() == 0)
177 return;
178 const Vng::Acceptance accept = response ? Vng::Accepted : Vng::Rejected;
180 if (scope == ItemScope) {
181 File file = m_changeSet.files()[m_fileIndex];
182 if (m_hunkIndex == 0) {
183 if (!response && file.oldFileName().isEmpty()
184 || response && file.fileName().isEmpty()) { // new file that is not added, removed file that we want added.
185 setResponse(response, BlockScope);
186 return;
188 file.setRenameAcceptance(accept);
190 else if (m_hunkIndex == 1)
191 file.setProtectionAcceptance(accept);
192 else {
193 Hunk hunk = file.hunks()[m_hunkIndex - 2];
194 hunk.setAcceptance(m_subHunkIndex, accept);
196 return;
199 int fileIndex = m_fileIndex;
200 int hunkIndex = m_hunkIndex;
202 do {
203 File file = m_changeSet.files()[fileIndex];
204 QList<Hunk> hunks = file.hunks();
205 do {
206 if (hunkIndex == 0)
207 file.setRenameAcceptance(accept);
208 else if (hunkIndex == 1)
209 file.setProtectionAcceptance(accept);
210 else
211 hunks[hunkIndex - 2].setAcceptance(accept);
212 hunkIndex++;
213 } while(scope >= BlockScope && hunkIndex -2 < hunks.count());
214 hunkIndex = 0;
215 fileIndex++;
216 } while(scope == FullRange && fileIndex < m_changeSet.count());
219 int HunksCursor::count()
221 return m_totalHunks;
224 void HunksCursor::forceCount()
226 if (m_totalHunks < 0) {
227 int total = 0;
228 foreach(File file, m_changeSet.files()) {
229 if (file.fileName() != file.oldFileName())
230 total++;
231 else if (file.protection() != file.oldProtection())
232 total++;
233 foreach(Hunk hunk, file.hunks())
234 total += hunk.subHunkCount();
236 m_totalHunks = total;
240 int HunksCursor::currentIndex() const
242 int answer = 1;
243 QList<File> files = m_changeSet.files().mid(0, m_fileIndex + 1);
244 QList<File>::Iterator iter = files.begin();
245 while(iter != files.end()) {
246 File file = *iter;
247 const bool renamed = file.fileName() != file.oldFileName();
248 const bool protectionChanged = !renamed && file.protection() != file.oldProtection();
249 QList<Hunk> hunks = file.hunks();
250 ++iter;
251 if (iter == files.end() && m_fileIndex < m_changeSet.count()) {
252 if (m_hunkIndex > 0 && renamed)
253 answer++;
254 if (m_hunkIndex > 1 && protectionChanged)
255 answer++;
256 hunks = hunks.mid(0, m_hunkIndex - 2);
258 else {
259 if (renamed)
260 answer++;
261 if (protectionChanged)
262 answer++;
264 QList<Hunk>::Iterator iter2 = hunks.begin();
265 int index = 2;
266 while(iter2 != hunks.end()) {
267 if (isValid() && iter == files.end() && m_hunkIndex < index++)
268 break;
269 answer += (*iter2).subHunkCount();
270 ++iter2;
273 return answer + m_subHunkIndex;
276 QString HunksCursor::currentText() const
278 QByteArray bytes;
279 if (m_changeSet.count() <= m_fileIndex)
280 return QString();
281 File file = m_changeSet.files()[m_fileIndex];
283 QString output;
284 QTextStream ts(&output);
285 if (m_config)
286 m_config->colorize(ts);
287 if (m_hunkIndex == 0) { // file rename
288 if (file.fileName().isEmpty())
289 ts << "remove ";
290 else if (file.oldFileName().isEmpty())
291 ts << "add ";
292 else
293 ts << "move ";
295 else if (m_hunkIndex == 1) // protections change
296 ts << "mode change ";
297 else
298 ts << "hunk ";
299 if (m_config)
300 m_config->normalColor(ts);
301 if (m_hunkIndex == 0) {
302 if (!file.oldFileName().isEmpty())
303 ts << "`" << file.oldFileName() << "' ";
304 if (! file.fileName().isEmpty())
305 ts << "`" << file.fileName() << "'";
307 else if (m_hunkIndex == 1)
308 ts << file.fileName() << " " << file.oldProtection() << " => " << file.protection() << endl;
310 if (m_hunkIndex < 2) {
311 ts << endl;
312 ts.flush();
313 return output;
316 if (file.count() <= m_hunkIndex - 2)
317 return "no change\n";
318 Hunk hunk = file.hunks()[m_hunkIndex - 2];
319 bytes.append(file.fileName());
320 bytes.append(" ");
321 bytes.append(QString::number(hunk.lineNumber(m_subHunkIndex)));
322 //bytes.append("[subhunk "+ QString::number(m_subHunkIndex + 1) +"/"+ QString::number(hunk.subHunkCount()) +"]");
323 bytes.append("\n");
325 QByteArray patch = hunk.subHunk(m_subHunkIndex);
326 //m_config.normalColor(out);
327 if (patch.contains((char) 0)) { // binary
328 //m_config.colorize(out);
329 bytes.append(QString("binary data\n"));
330 //m_config.normalColor(out);
332 else
333 bytes.append(patch);
335 ts.flush();
336 return output + QString(bytes);
339 QString HunksCursor::helpMessage() const
341 return QString(
342 "How to use revert...\n"
343 "y: revert this patch\n"
344 "n: don't revert it\n\n"
346 "s: don't revert the rest of the changes to this file\n"
347 "f: revert the rest of the changes to this file\n\n"
349 "d: revert selected patches, skipping all the remaining patches\n"
350 "a: revert all the remaining patches\n"
351 "q: cancel revert\n\n"
353 "j: skip to next patch\n"
354 "k: back up to previous patch\n"
355 "c: calculate number of patches\n"
356 "h or ?: show this help\n");
359 int HunksCursor::currentSubHunkCount()
361 File file = m_changeSet.files()[m_fileIndex];
362 if (m_hunkIndex < 2)
363 return 1;
364 Hunk hunk = file.hunks()[m_hunkIndex - 2];
365 return hunk.subHunkCount();
368 bool HunksCursor::isValid() const
370 return m_changeSet.count() > 0 && m_changeSet.count() > m_fileIndex;
373 QString HunksCursor::allowedOptions() const
375 QString allowed = "ynsfqadjk";
376 if (m_totalHunks == -1)
377 allowed += "c";
378 return allowed;