From d7cddd4e1764d6513094bb47ccd2e73f2f4512c8 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Mon, 28 Mar 2011 11:59:26 +0200
Subject: [PATCH] VCS[Bazaar, Mercurial]: Add diff formatting, revert chunk.

Add toolbar controls for ignore-whitespace to editors
and wire 'Revert Chunk' context menus.

Fix Bazaar diff interaction (find the file on doubleclick).

Introduce initializeDiffEditor to BaseClient and wire the editors
there.
---
 src/plugins/bazaar/bazaarclient.cpp       | 95 +++++++++++++++++++++-
 src/plugins/bazaar/bazaarclient.h         | 11 ++-
 src/plugins/bazaar/bazaareditor.cpp       | 24 +++---
 src/plugins/mercurial/mercurialclient.cpp | 96 +++++++++++++++++++++--
 src/plugins/mercurial/mercurialclient.h   | 10 ++-
 src/plugins/mercurial/mercurialeditor.cpp | 20 ++---
 src/plugins/vcsbase/vcsbaseclient.cpp     | 12 ++-
 src/plugins/vcsbase/vcsbaseclient.h       |  8 +-
 src/plugins/vcsbase/vcsbaseeditor.cpp     |  2 +
 src/plugins/vcsbase/vcsbaseplugin.cpp     |  2 +-
 10 files changed, 246 insertions(+), 34 deletions(-)

diff --git a/src/plugins/bazaar/bazaarclient.cpp b/src/plugins/bazaar/bazaarclient.cpp
index e45f0b4f374..8ec9cf88a46 100644
--- a/src/plugins/bazaar/bazaarclient.cpp
+++ b/src/plugins/bazaar/bazaarclient.cpp
@@ -36,6 +36,10 @@
 
 #include <vcsbase/vcsbaseclientsettings.h>
 #include <vcsbase/vcsbaseplugin.h>
+#include <vcsbase/vcsbaseeditor.h>
+#include <vcsbase/vcsbaseeditorparameterwidget.h>
+
+#include <utils/qtcassert.h>
 
 #include <QtCore/QDir>
 #include <QtCore/QFileInfo>
@@ -314,9 +318,23 @@ QStringList BazaarClient::annotateArguments(const QString &file,
     return args << file;
 }
 
-QStringList BazaarClient::diffArguments(const QStringList &files) const
+QStringList BazaarClient::diffArguments(const QStringList &files,
+                                        const ExtraCommandOptions &extraOptions) const
 {
     QStringList args;
+    foreach (const QVariant &extraOption, extraOptions) {
+        switch (extraOption.type()) {
+        case QVariant::String:
+            args.append(extraOption.toString());
+            break;
+        case QVariant::StringList:
+            args.append(extraOption.toStringList());
+            break;
+        default:
+            QTC_ASSERT(false, continue; )
+            break;
+        }
+    }
     if (!files.isEmpty())
         args.append(files);
     return args;
@@ -324,7 +342,7 @@ QStringList BazaarClient::diffArguments(const QStringList &files) const
 
 QStringList BazaarClient::logArguments(const QStringList &files) const
 {
-    return diffArguments(files);
+    return diffArguments(files, ExtraCommandOptions());
 }
 
 QStringList BazaarClient::statusArguments(const QString &file) const
@@ -410,5 +428,78 @@ QStringList BazaarClient::commonPullOrPushArguments(const ExtraCommandOptions &e
     return args;
 }
 
+// Collect all parameters required for a diff to be able to associate them
+// with a diff editor and re-run the diff with parameters.
+struct BazaarDiffParameters
+{
+    QString workingDir;
+    QStringList files;
+    VCSBase::VCSBaseClient::ExtraCommandOptions extraOptions;
+};
+
+// Parameter widget controlling whitespace diff mode, associated with a parameter
+class BazaarDiffParameterWidget : public VCSBase::VCSBaseEditorParameterWidget
+{
+    Q_OBJECT
+public:
+    explicit BazaarDiffParameterWidget(const BazaarDiffParameters &p, QWidget *parent = 0);
+
+signals:
+    void reRunDiff(const Bazaar::Internal::BazaarDiffParameters &);
+
+private slots:
+    void triggerReRun();
+
+private:
+    const BazaarDiffParameters m_parameters;
+};
+
+BazaarDiffParameterWidget::BazaarDiffParameterWidget(const BazaarDiffParameters &p, QWidget *parent) :
+    VCSBase::VCSBaseEditorParameterWidget(parent), m_parameters(p)
+{
+    addIgnoreWhiteSpaceButton(QLatin1String("-w"));
+    addIgnoreBlankLinesButton(QLatin1String("-B"));
+    connect(this, SIGNAL(argumentsChanged()), this, SLOT(triggerReRun()));
+}
+
+void BazaarDiffParameterWidget::triggerReRun()
+{
+    BazaarDiffParameters effectiveParameters = m_parameters;
+    // Bazaar wants "--diff-options=-w -B.."
+    const QStringList formatArguments = arguments();
+    if (!formatArguments.isEmpty()) {
+        const QString a = QLatin1String("--diff-options=")
+                          + formatArguments.join(QString(QLatin1Char(' ')));
+        effectiveParameters.extraOptions.insert(42, QVariant(a));
+    }
+    emit reRunDiff(effectiveParameters);
+}
+
+void BazaarClient::bazaarDiff(const Bazaar::Internal::BazaarDiffParameters &p)
+{
+    diff(p.workingDir, p.files, p.extraOptions);
+}
+
+void BazaarClient::initializeDiffEditor(const QString &workingDir, const QStringList &files,
+                                        const VCSBase::VCSBaseClient::ExtraCommandOptions &extra,
+                                        VCSBase::VCSBaseEditorWidget *diffEditorWidget)
+{
+    // Wire up the parameter widget to trigger a re-run on
+    // parameter change and 'revert' from inside the diff editor.
+    BazaarDiffParameters parameters;
+    parameters.workingDir = workingDir;
+    parameters.files = files;
+    parameters.extraOptions = extra;
+    diffEditorWidget->setRevertDiffChunkEnabled(true);
+    BazaarDiffParameterWidget *pw = new BazaarDiffParameterWidget(parameters);
+    connect(pw, SIGNAL(reRunDiff(Bazaar::Internal::BazaarDiffParameters)),
+            this, SLOT(bazaarDiff(Bazaar::Internal::BazaarDiffParameters)));
+    connect(diffEditorWidget, SIGNAL(diffChunkReverted(VCSBase::DiffChunk)),
+            pw, SLOT(triggerReRun()));
+    diffEditorWidget->setConfigurationWidget(pw);
+}
+
 } //namespace Internal
 } // namespace Bazaar
+
+#include "bazaarclient.moc"
diff --git a/src/plugins/bazaar/bazaarclient.h b/src/plugins/bazaar/bazaarclient.h
index e1118b3a814..79ad273241b 100644
--- a/src/plugins/bazaar/bazaarclient.h
+++ b/src/plugins/bazaar/bazaarclient.h
@@ -39,9 +39,11 @@
 
 namespace Bazaar {
 namespace Internal {
+struct BazaarDiffParameters;
 
 class BazaarClient : public VCSBase::VCSBaseClient
 {
+    Q_OBJECT
 public:
     enum ExtraOptionId
     {
@@ -75,6 +77,9 @@ public:
     BranchInfo synchronousBranchQuery(const QString &repositoryRoot) const;
     virtual QString findTopLevelForFile(const QFileInfo &file) const;
 
+private slots:
+    void bazaarDiff(const Bazaar::Internal::BazaarDiffParameters &p);
+
 protected:
     virtual QString vcsEditorKind(VCSCommand cmd) const;
 
@@ -94,7 +99,11 @@ protected:
     virtual QStringList revertAllArguments(const QString &revision) const;
     virtual QStringList annotateArguments(const QString &file,
                                           const QString &revision, int lineNumber) const;
-    virtual QStringList diffArguments(const QStringList &files) const;
+    virtual QStringList diffArguments(const QStringList &files,
+                                      const ExtraCommandOptions &extraOptions) const;
+    virtual void initializeDiffEditor(const QString &workingDir, const QStringList &files,
+                                      const VCSBase::VCSBaseClient::ExtraCommandOptions &extra,
+                                      VCSBase::VCSBaseEditorWidget *diffEditorWidget);
     virtual QStringList logArguments(const QStringList &files) const;
     virtual QStringList statusArguments(const QString &file) const;
     virtual QStringList viewArguments(const QString &revision) const;
diff --git a/src/plugins/bazaar/bazaareditor.cpp b/src/plugins/bazaar/bazaareditor.cpp
index b6e2c484110..f0beeeea57c 100644
--- a/src/plugins/bazaar/bazaareditor.cpp
+++ b/src/plugins/bazaar/bazaareditor.cpp
@@ -107,16 +107,22 @@ VCSBase::BaseAnnotationHighlighter *BazaarEditor::createAnnotationHighlighter(co
     return new BazaarAnnotationHighlighter(changes);
 }
 
-QString BazaarEditor::fileNameFromDiffSpecification(const QTextBlock &diffFileSpec) const
+QString BazaarEditor::fileNameFromDiffSpecification(const QTextBlock &inBlock) const
 {
-    const QString filechangeId(QLatin1String("+++ b/"));
-    QTextBlock::iterator iterator;
-    for (iterator = diffFileSpec.begin(); !(iterator.atEnd()); iterator++) {
-        QTextFragment fragment = iterator.fragment();
-        if(fragment.isValid()) {
-            if (fragment.text().startsWith(filechangeId)) {
-                const QString filename = fragment.text().remove(0, filechangeId.size());
-                return findDiffFile(filename, BazaarPlugin::instance()->versionControl());
+    // Check for:
+    // === modified file 'mainwindow.cpp'
+    // --- mainwindow.cpp<tab>2011-03-28 08:12:28 +0000
+    // +++ mainwindow.cpp<tab>2011-03-28 08:53:55 +0000
+    const QString newFileIndicator = QLatin1String("+++ ");
+    const QChar tab = QLatin1Char('\t');
+    for (QTextBlock  block = inBlock; block.isValid(); block = block.previous()) {
+        const QString line = block.text();
+        if (line.startsWith(newFileIndicator)) {
+            const int tabIndex = line.indexOf(tab);
+            if (tabIndex != -1) {
+                const QString diffFileName = line.mid(newFileIndicator.size(),
+                                                      tabIndex - newFileIndicator.size());
+                return findDiffFile(diffFileName);
             }
         }
     }
diff --git a/src/plugins/mercurial/mercurialclient.cpp b/src/plugins/mercurial/mercurialclient.cpp
index ed1a1cdf8d9..7e6108be72e 100644
--- a/src/plugins/mercurial/mercurialclient.cpp
+++ b/src/plugins/mercurial/mercurialclient.cpp
@@ -36,14 +36,17 @@
 
 #include <vcsbase/vcsbaseoutputwindow.h>
 #include <vcsbase/vcsbaseplugin.h>
+#include <vcsbase/vcsbaseeditor.h>
+#include <vcsbase/vcsbaseeditorparameterwidget.h>
 #include <vcsbase/vcsjobrunner.h>
 #include <utils/synchronousprocess.h>
+#include <utils/qtcassert.h>
 
-#include <QDir>
-#include <QFileInfo>
-#include <QTextCodec>
-#include <QTextStream>
-#include <QVariant>
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+#include <QtCore/QTextCodec>
+#include <QtCore/QTextStream>
+#include <QtCore/QVariant>
 
 namespace Mercurial {
 namespace Internal  {
@@ -429,10 +432,24 @@ QStringList MercurialClient::annotateArguments(const QString &file,
     return args << file;
 }
 
-QStringList MercurialClient::diffArguments(const QStringList &files) const
+QStringList MercurialClient::diffArguments(const QStringList &files,
+                                           const ExtraCommandOptions &extraOptions) const
 {
     QStringList args;
     args << QLatin1String("-g") << QLatin1String("-p") << QLatin1String("-U 8");
+    foreach (const QVariant &extraOption, extraOptions) {
+        switch (extraOption.type()) {
+        case QVariant::String:
+            args.append(extraOption.toString());
+            break;
+        case QVariant::StringList:
+            args.append(extraOption.toStringList());
+            break;
+        default:
+            QTC_ASSERT(false, continue; )
+            break;
+        }
+    }
     if (!files.isEmpty())
         args.append(files);
     return args;
@@ -487,5 +504,72 @@ QPair<QString, QString> MercurialClient::parseStatusLine(const QString &line) co
     return status;
 }
 
+// Collect all parameters required for a diff to be able to associate them
+// with a diff editor and re-run the diff with parameters.
+struct MercurialDiffParameters
+{
+    QString workingDir;
+    QStringList files;
+    VCSBase::VCSBaseClient::ExtraCommandOptions extraOptions;
+};
+
+// Parameter widget controlling whitespace diff mode, associated with a parameter
+class MercurialDiffParameterWidget : public VCSBase::VCSBaseEditorParameterWidget
+{
+    Q_OBJECT
+public:
+    explicit MercurialDiffParameterWidget(const MercurialDiffParameters &p, QWidget *parent = 0);
+
+signals:
+    void reRunDiff(const Mercurial::Internal::MercurialDiffParameters &);
+
+private slots:
+    void triggerReRun();
+
+private:
+    const MercurialDiffParameters m_parameters;
+};
+
+MercurialDiffParameterWidget::MercurialDiffParameterWidget(const MercurialDiffParameters &p, QWidget *parent) :
+    VCSBase::VCSBaseEditorParameterWidget(parent), m_parameters(p)
+{
+    addIgnoreWhiteSpaceButton(QLatin1String("-w"));
+    addIgnoreBlankLinesButton(QLatin1String("-B"));
+    connect(this, SIGNAL(argumentsChanged()), this, SLOT(triggerReRun()));
+}
+
+void MercurialDiffParameterWidget::triggerReRun()
+{
+    MercurialDiffParameters effectiveParameters = m_parameters;
+    effectiveParameters.extraOptions.insert(42, QVariant(arguments()));
+    emit reRunDiff(effectiveParameters);
+}
+
+void MercurialClient::mercurialDiff(const Mercurial::Internal::MercurialDiffParameters &p)
+{
+    diff(p.workingDir, p.files, p.extraOptions);
+}
+
+void MercurialClient::initializeDiffEditor(const QString &workingDir, const QStringList &files,
+                                           const VCSBase::VCSBaseClient::ExtraCommandOptions &extra,
+                                           VCSBase::VCSBaseEditorWidget *diffEditorWidget)
+{
+    // Wire up the parameter widget to trigger a re-run on
+    // parameter change and 'revert' from inside the diff editor.
+    MercurialDiffParameters parameters;
+    parameters.workingDir = workingDir;
+    parameters.files = files;
+    parameters.extraOptions = extra;
+    diffEditorWidget->setRevertDiffChunkEnabled(true);
+    MercurialDiffParameterWidget *pw = new MercurialDiffParameterWidget(parameters);
+    connect(pw, SIGNAL(reRunDiff(Mercurial::Internal::MercurialDiffParameters)),
+            this, SLOT(mercurialDiff(Mercurial::Internal::MercurialDiffParameters)));
+    connect(diffEditorWidget, SIGNAL(diffChunkReverted(VCSBase::DiffChunk)),
+            pw, SLOT(triggerReRun()));
+    diffEditorWidget->setConfigurationWidget(pw);
+}
+
 } // namespace Internal
 } // namespace Mercurial
+
+#include "mercurialclient.moc"
diff --git a/src/plugins/mercurial/mercurialclient.h b/src/plugins/mercurial/mercurialclient.h
index b36679ca781..278c1e9894e 100644
--- a/src/plugins/mercurial/mercurialclient.h
+++ b/src/plugins/mercurial/mercurialclient.h
@@ -38,6 +38,7 @@
 
 namespace Mercurial {
 namespace Internal {
+struct MercurialDiffParameters;
 
 class MercurialClient : public VCSBase::VCSBaseClient
 {
@@ -71,6 +72,9 @@ public:
     void outgoing(const QString &repositoryRoot);
     QString vcsGetRepositoryURL(const QString &directory);
 
+private slots:
+    void mercurialDiff(const Mercurial::Internal::MercurialDiffParameters &);
+
 public:
     virtual QString findTopLevelForFile(const QFileInfo &file) const;
 
@@ -93,7 +97,11 @@ protected:
     virtual QStringList revertAllArguments(const QString &revision) const;
     virtual QStringList annotateArguments(const QString &file,
                                           const QString &revision, int lineNumber) const;
-    virtual QStringList diffArguments(const QStringList &files) const;
+    virtual QStringList diffArguments(const QStringList &files,
+                                      const ExtraCommandOptions &extraOptions) const;
+    virtual void initializeDiffEditor(const QString &workingDir, const QStringList &files,
+                                      const VCSBase::VCSBaseClient::ExtraCommandOptions &extra,
+                                      VCSBase::VCSBaseEditorWidget *ed);
     virtual QStringList logArguments(const QStringList &files) const;
     virtual QStringList statusArguments(const QString &file) const;
     virtual QStringList viewArguments(const QString &revision) const;
diff --git a/src/plugins/mercurial/mercurialeditor.cpp b/src/plugins/mercurial/mercurialeditor.cpp
index 3c5a94152f8..3f425de82be 100644
--- a/src/plugins/mercurial/mercurialeditor.cpp
+++ b/src/plugins/mercurial/mercurialeditor.cpp
@@ -102,18 +102,18 @@ VCSBase::BaseAnnotationHighlighter *MercurialEditor::createAnnotationHighlighter
     return new MercurialAnnotationHighlighter(changes);
 }
 
-QString MercurialEditor::fileNameFromDiffSpecification(const QTextBlock &diffFileSpec) const
+QString MercurialEditor::fileNameFromDiffSpecification(const QTextBlock &inBlock) const
 {
-   const QString filechangeId(QLatin1String("+++ b/"));
-    QTextBlock::iterator iterator;
-    for (iterator = diffFileSpec.begin(); !(iterator.atEnd()); iterator++) {
-        QTextFragment fragment = iterator.fragment();
-        if(fragment.isValid()) {
-            if (fragment.text().startsWith(filechangeId)) {
-                const QString filename = fragment.text().remove(0, filechangeId.size());
-                return findDiffFile(filename, MercurialPlugin::instance()->versionControl());
-            }
+    // git-compatible format: check for "+++ b/src/plugins/git/giteditor.cpp" (blame and diff)
+    // Go back chunks.
+    const QString newFileIndicator = QLatin1String("+++ b/");
+    for (QTextBlock  block = inBlock; block.isValid(); block = block.previous()) {
+        QString diffFileName = block.text();
+        if (diffFileName.startsWith(newFileIndicator)) {
+            diffFileName.remove(0, newFileIndicator.size());
+            return findDiffFile(diffFileName, MercurialPlugin::instance()->versionControl());
         }
+
     }
     return QString();
 }
diff --git a/src/plugins/vcsbase/vcsbaseclient.cpp b/src/plugins/vcsbase/vcsbaseclient.cpp
index 66f0c4b2ec3..03ef4234f61 100644
--- a/src/plugins/vcsbase/vcsbaseclient.cpp
+++ b/src/plugins/vcsbase/vcsbaseclient.cpp
@@ -262,11 +262,12 @@ void VCSBaseClient::annotate(const QString &workingDir, const QString &file,
     enqueueJob(job);
 }
 
-void VCSBaseClient::diff(const QString &workingDir, const QStringList &files)
+void VCSBaseClient::diff(const QString &workingDir, const QStringList &files,
+                         const ExtraCommandOptions &extraOptions)
 {
     const QString vcsCmdString = vcsCommandString(DiffCommand);
     QStringList args;
-    args << vcsCmdString << diffArguments(files);
+    args << vcsCmdString << diffArguments(files, extraOptions);
     const QString kind = vcsEditorKind(DiffCommand);
     const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDir, files);
     const QString title = vcsEditorTitle(vcsCmdString, id);
@@ -274,6 +275,7 @@ void VCSBaseClient::diff(const QString &workingDir, const QStringList &files)
     VCSBase::VCSBaseEditorWidget *editor = createVCSEditor(kind, title, source, true,
                                                            vcsCmdString.toLatin1().constData(), id);
     editor->setDiffBaseDirectory(workingDir);
+    initializeDiffEditor(workingDir, files, extraOptions, editor);
 
     QSharedPointer<VCSJob> job(new VCSJob(workingDir, args, editor));
     enqueueJob(job);
@@ -444,6 +446,12 @@ void VCSBaseClient::settingsChanged()
     }
 }
 
+void VCSBaseClient::initializeDiffEditor(const QString & /* workingDir */, const QStringList & /* files */,
+                                         const ExtraCommandOptions & /* extraOptions */,
+                                         VCSBaseEditorWidget *)
+{
+}
+
 QString VCSBaseClient::vcsEditorTitle(const QString &vcsCmd, const QString &sourceId) const
 {
     return QFileInfo(d->m_clientSettings.binary()).baseName() +
diff --git a/src/plugins/vcsbase/vcsbaseclient.h b/src/plugins/vcsbase/vcsbaseclient.h
index a9ef78eb7c6..e72483592eb 100644
--- a/src/plugins/vcsbase/vcsbaseclient.h
+++ b/src/plugins/vcsbase/vcsbaseclient.h
@@ -89,7 +89,8 @@ public:
                                  const ExtraCommandOptions &extraOptions = ExtraCommandOptions());
     void annotate(const QString &workingDir, const QString &file,
                   const QString revision = QString(), int lineNumber = -1);
-    void diff(const QString &workingDir, const QStringList &files = QStringList());
+    void diff(const QString &workingDir, const QStringList &files = QStringList(),
+              const ExtraCommandOptions &extraOptions = ExtraCommandOptions());
     void log(const QString &workingDir, const QStringList &files = QStringList(),
              bool enableAnnotationContextMenu = false);
     void status(const QString &workingDir, const QString &file = QString());
@@ -154,7 +155,10 @@ protected:
     virtual QStringList revertAllArguments(const QString &revision) const = 0;
     virtual QStringList annotateArguments(const QString &file,
                                           const QString &revision, int lineNumber) const = 0;
-    virtual QStringList diffArguments(const QStringList &files) const = 0;
+    virtual QStringList diffArguments(const QStringList &files,
+                                      const ExtraCommandOptions &extraOptions) const = 0;
+    virtual void initializeDiffEditor(const QString &workingDir, const QStringList &files,
+                                      const ExtraCommandOptions &extraOptions, VCSBaseEditorWidget *ed);
     virtual QStringList logArguments(const QStringList &files) const = 0;
     virtual QStringList statusArguments(const QString &file) const = 0;
     virtual QStringList viewArguments(const QString &revision) const = 0;
diff --git a/src/plugins/vcsbase/vcsbaseeditor.cpp b/src/plugins/vcsbase/vcsbaseeditor.cpp
index 4e5da7bf9d0..c8797743323 100644
--- a/src/plugins/vcsbase/vcsbaseeditor.cpp
+++ b/src/plugins/vcsbase/vcsbaseeditor.cpp
@@ -700,6 +700,8 @@ DiffChunk VCSBaseEditorWidget::diffChunk(QTextCursor cursor) const
         return rc;
     // Concatenate chunk and convert
     QString unicode = block.text();
+    if (!unicode.endsWith(QLatin1Char('\n'))) // Missing in case of hg.
+        unicode.append(QLatin1Char('\n'));
     for (block = block.next() ; block.isValid() ; block = block.next()) {
         const QString line = block.text();
         if (checkChunkLine(line, &chunkStart)) {
diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp
index 24f28b86510..0df8d519f05 100644
--- a/src/plugins/vcsbase/vcsbaseplugin.cpp
+++ b/src/plugins/vcsbase/vcsbaseplugin.cpp
@@ -927,7 +927,7 @@ bool VCSBasePlugin::runPatch(const QByteArray &input, const QString &workingDire
         return false;
     }
     if (patchProcess.exitCode() != 0) {
-        ow->appendError(tr("'%1' failed (exit code %2).").arg(patchProcess.exitCode()));
+        ow->appendError(tr("'%1' failed (exit code %2).").arg(patch).arg(patchProcess.exitCode()));
         return false;
     }
     return true;
-- 
GitLab