From 84f2875f6d410588bd01f1d3a573b1ec0f02ef3c Mon Sep 17 00:00:00 2001
From: Orgad Shaneh <orgad.shaneh@audiocodes.com>
Date: Sun, 7 Feb 2016 23:35:41 +0200
Subject: [PATCH] Git: Support tree argument for Grep

Change-Id: Ic7dfcd8bad98223d68725f1a0c1f103ad8ea4e0f
Reviewed-by: Eike Ziller <eike.ziller@theqtcompany.com>
Reviewed-by: Tobias Hunger <tobias.hunger@theqtcompany.com>
---
 src/plugins/git/gitgrep.cpp             | 98 ++++++++++++++++++++++---
 src/plugins/git/gitgrep.h               | 11 ++-
 src/plugins/texteditor/basefilefind.cpp | 31 ++++----
 src/plugins/texteditor/basefilefind.h   |  6 +-
 4 files changed, 120 insertions(+), 26 deletions(-)

diff --git a/src/plugins/git/gitgrep.cpp b/src/plugins/git/gitgrep.cpp
index 57ce4afb22..390a546f64 100644
--- a/src/plugins/git/gitgrep.cpp
+++ b/src/plugins/git/gitgrep.cpp
@@ -27,21 +27,26 @@
 #include "gitclient.h"
 #include "gitplugin.h"
 
+#include <coreplugin/editormanager/editormanager.h>
 #include <coreplugin/progressmanager/progressmanager.h>
 #include <coreplugin/vcsmanager.h>
 #include <texteditor/findinfiles.h>
 #include <vcsbase/vcscommand.h>
 #include <vcsbase/vcsbaseconstants.h>
 
+#include <utils/fancylineedit.h>
 #include <utils/filesearch.h>
 #include <utils/fileutils.h>
 #include <utils/qtcassert.h>
 #include <utils/runextensions.h>
 #include <utils/synchronousprocess.h>
+#include <utils/textfileformat.h>
 
 #include <QCheckBox>
 #include <QFuture>
 #include <QFutureWatcher>
+#include <QHBoxLayout>
+#include <QRegularExpressionValidator>
 #include <QScopedPointer>
 #include <QSettings>
 #include <QTextStream>
@@ -49,6 +54,13 @@
 namespace Git {
 namespace Internal {
 
+class GitGrepParameters
+{
+public:
+    QString ref;
+    bool isEnabled = false;
+};
+
 using namespace Core;
 using namespace Utils;
 using VcsBase::VcsCommand;
@@ -56,6 +68,7 @@ using VcsBase::VcsCommand;
 namespace {
 
 const char EnableGitGrep[] = "EnableGitGrep";
+const char GitGrepRef[] = "GitGrepRef";
 
 class GitGrepRunner : public QObject
 {
@@ -78,8 +91,10 @@ public:
         static const QLatin1String resetColor("\x1b[m");
         FileSearchResult single;
         const int lineSeparator = line.indexOf(QChar::Null);
-        single.fileName = m_directory + QLatin1Char('/')
-                + line.left(lineSeparator);
+        QString filePath = line.left(lineSeparator);
+        if (!m_ref.isEmpty() && filePath.startsWith(m_ref))
+            filePath.remove(0, m_ref.length());
+        single.fileName = m_directory + QLatin1Char('/') + filePath;
         const int textSeparator = line.indexOf(QChar::Null, lineSeparator + 1);
         single.lineNumber = line.mid(lineSeparator + 1, textSeparator - lineSeparator - 1).toInt();
         QString text = line.mid(textSeparator + 1);
@@ -130,6 +145,11 @@ public:
         else
             arguments << QLatin1String("-F");
         arguments << m_parameters.text;
+        GitGrepParameters params = m_parameters.extensionParameters.value<GitGrepParameters>();
+        if (!params.ref.isEmpty()) {
+            arguments << params.ref;
+            m_ref = params.ref + QLatin1Char(':');
+        }
         arguments << QLatin1String("--") << m_parameters.nameFilters;
         GitClient *client = GitPlugin::instance()->client();
         QScopedPointer<VcsCommand> command(client->createCommand(m_directory));
@@ -166,6 +186,7 @@ public:
 private:
     FutureInterfaceType m_fi;
     QString m_directory;
+    QString m_ref;
     const TextEditor::FileFindParameters &m_parameters;
 };
 
@@ -180,9 +201,21 @@ static bool validateDirectory(const QString &path)
 
 GitGrep::GitGrep()
 {
-    m_widget = new QCheckBox(tr("&Use Git Grep"));
-    m_widget->setToolTip(tr("Use Git Grep for searching. This includes only files "
-                            "that are managed by source control."));
+    m_widget = new QWidget;
+    auto layout = new QHBoxLayout(m_widget);
+    layout->setMargin(0);
+    m_enabledCheckBox = new QCheckBox(tr("&Use Git Grep"));
+    m_enabledCheckBox->setToolTip(tr("Use Git Grep for searching. This includes only files "
+                                     "that are managed by Git."));
+    layout->addWidget(m_enabledCheckBox);
+    m_treeLineEdit = new FancyLineEdit;
+    m_treeLineEdit->setPlaceholderText(
+                tr("Tree: add reference here or leave empty to search through the file system)"));
+    m_treeLineEdit->setToolTip(
+                tr("Reference can be HEAD, tag, local or remote branch, or a commit hash."));
+    const QRegularExpression refExpression(QLatin1String("[\\w/]*"));
+    m_treeLineEdit->setValidator(new QRegularExpressionValidator(refExpression, this));
+    layout->addWidget(m_treeLineEdit);
     TextEditor::FindInFiles *findInFiles = TextEditor::FindInFiles::instance();
     QTC_ASSERT(findInFiles, return);
     connect(findInFiles, &TextEditor::FindInFiles::pathChanged,
@@ -202,6 +235,14 @@ QString GitGrep::title() const
     return tr("Git Grep");
 }
 
+QString GitGrep::toolTip() const
+{
+    const QString ref = m_treeLineEdit->text();
+    if (!ref.isEmpty())
+        return tr("Ref: %1\n%2").arg(ref);
+    return QLatin1String("%1");
+}
+
 QWidget *GitGrep::widget() const
 {
     return m_widget;
@@ -209,27 +250,32 @@ QWidget *GitGrep::widget() const
 
 bool GitGrep::isEnabled() const
 {
-    return m_widget->isEnabled() && m_widget->isChecked();
+    return m_widget->isEnabled() && m_enabledCheckBox->isChecked();
 }
 
 bool GitGrep::isEnabled(const TextEditor::FileFindParameters &parameters) const
 {
-    return parameters.extensionParameters.toBool();
+    return parameters.extensionParameters.value<GitGrepParameters>().isEnabled;
 }
 
 QVariant GitGrep::parameters() const
 {
-    return isEnabled();
+    GitGrepParameters params;
+    params.isEnabled = isEnabled();
+    params.ref = m_treeLineEdit->text();
+    return qVariantFromValue(params);
 }
 
 void GitGrep::readSettings(QSettings *settings)
 {
-    m_widget->setChecked(settings->value(QLatin1String(EnableGitGrep), false).toBool());
+    m_enabledCheckBox->setChecked(settings->value(QLatin1String(EnableGitGrep), false).toBool());
+    m_treeLineEdit->setText(settings->value(QLatin1String(GitGrepRef)).toString());
 }
 
 void GitGrep::writeSettings(QSettings *settings) const
 {
-    settings->setValue(QLatin1String(EnableGitGrep), m_widget->isChecked());
+    settings->setValue(QLatin1String(EnableGitGrep), m_enabledCheckBox->isChecked());
+    settings->setValue(QLatin1String(GitGrepRef), m_treeLineEdit->text());
 }
 
 QFuture<FileSearchResultList> GitGrep::executeSearch(
@@ -238,5 +284,37 @@ QFuture<FileSearchResultList> GitGrep::executeSearch(
     return Utils::runAsync(GitGrepRunner::run, parameters);
 }
 
+IEditor *GitGrep::openEditor(const SearchResultItem &item,
+                             const TextEditor::FileFindParameters &parameters)
+{
+    GitGrepParameters params = parameters.extensionParameters.value<GitGrepParameters>();
+    if (!params.isEnabled || params.ref.isEmpty() || item.path.isEmpty())
+        return nullptr;
+    const QString path = QDir::fromNativeSeparators(item.path.first());
+    QByteArray content;
+    GitClient *client = GitPlugin::instance()->client();
+    const QString topLevel = parameters.additionalParameters.toString();
+    const QString relativePath = QDir(topLevel).relativeFilePath(path);
+    if (!client->synchronousShow(topLevel, params.ref + QLatin1String(":./") + relativePath,
+                                 &content, nullptr)) {
+        return nullptr;
+    }
+    if (content.isEmpty())
+        return nullptr;
+    QByteArray fileContent;
+    if (TextFileFormat::readFileUTF8(path, 0, &fileContent, 0) == TextFileFormat::ReadSuccess) {
+        if (fileContent == content)
+            return nullptr; // open the file for read/write
+    }
+    QString title = tr("Git Show %1:%2").arg(params.ref).arg(relativePath);
+    IEditor *editor = EditorManager::openEditorWithContents(Id(), &title, content, title,
+                                                            EditorManager::DoNotSwitchToDesignMode);
+    editor->gotoLine(item.lineNumber, item.textMarkPos);
+    editor->document()->setTemporary(true);
+    return editor;
+}
+
 } // Internal
 } // Git
+
+Q_DECLARE_METATYPE(Git::Internal::GitGrepParameters)
diff --git a/src/plugins/git/gitgrep.h b/src/plugins/git/gitgrep.h
index e0a5ea60f1..a18f8ae7fc 100644
--- a/src/plugins/git/gitgrep.h
+++ b/src/plugins/git/gitgrep.h
@@ -28,10 +28,12 @@
 
 #include <texteditor/basefilefind.h>
 
-#include <utils/fileutils.h>
+#include <QCoreApplication>
 
 QT_FORWARD_DECLARE_CLASS(QCheckBox)
 
+namespace Utils { class FancyLineEdit; }
+
 namespace Git {
 namespace Internal {
 
@@ -43,6 +45,7 @@ public:
     GitGrep();
     ~GitGrep() override;
     QString title() const override;
+    QString toolTip() const override;
     QWidget *widget() const override;
     bool isEnabled() const override;
     bool isEnabled(const TextEditor::FileFindParameters &parameters) const override;
@@ -51,9 +54,13 @@ public:
     void writeSettings(QSettings *settings) const override;
     QFuture<Utils::FileSearchResultList> executeSearch(
             const TextEditor::FileFindParameters &parameters) override;
+    Core::IEditor *openEditor(const Core::SearchResultItem &item,
+                              const TextEditor::FileFindParameters &parameters) override;
 
 private:
-    QCheckBox *m_widget;
+    QWidget *m_widget;
+    QCheckBox *m_enabledCheckBox;
+    Utils::FancyLineEdit *m_treeLineEdit;
 };
 
 } // namespace Internal
diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp
index bb8c9f2f1c..facac8a9c0 100644
--- a/src/plugins/texteditor/basefilefind.cpp
+++ b/src/plugins/texteditor/basefilefind.cpp
@@ -140,8 +140,11 @@ void BaseFileFind::runNewSearch(const QString &txt, FindFlags findFlags,
     d->m_currentFindSupport = 0;
     if (d->m_filterCombo)
         updateComboEntries(d->m_filterCombo, true);
+    QString tooltip = toolTip();
+    if (d->m_extension)
+        tooltip = tooltip.arg(d->m_extension->toolTip());
     SearchResult *search = SearchResultWindow::instance()->startNewSearch(label(),
-                           toolTip().arg(IFindFilter::descriptionForFindFlags(findFlags)),
+                           tooltip.arg(IFindFilter::descriptionForFindFlags(findFlags)),
                            txt, searchMode, SearchResultWindow::PreserveCaseEnabled,
                            QString::fromLatin1("TextEditor"));
     search->setTextToReplace(txt);
@@ -320,14 +323,19 @@ void BaseFileFind::updateComboEntries(QComboBox *combo, bool onTop)
 void BaseFileFind::openEditor(const SearchResultItem &item)
 {
     SearchResult *result = qobject_cast<SearchResult *>(sender());
+    FileFindParameters parameters = result->userData().value<FileFindParameters>();
     IEditor *openedEditor = 0;
-    if (item.path.size() > 0) {
-        openedEditor = EditorManager::openEditorAt(QDir::fromNativeSeparators(item.path.first()),
-                                                   item.lineNumber,
-                                                   item.textMarkPos, Id(),
-                                                   EditorManager::DoNotSwitchToDesignMode);
-    } else {
-        openedEditor = EditorManager::openEditor(QDir::fromNativeSeparators(item.text));
+    if (d->m_extension)
+        openedEditor = d->m_extension->openEditor(item, parameters);
+    if (!openedEditor) {
+        if (item.path.size() > 0) {
+            openedEditor = EditorManager::openEditorAt(QDir::fromNativeSeparators(item.path.first()),
+                                                       item.lineNumber,
+                                                       item.textMarkPos, Id(),
+                                                       EditorManager::DoNotSwitchToDesignMode);
+        } else {
+            openedEditor = EditorManager::openEditor(QDir::fromNativeSeparators(item.text));
+        }
     }
     if (d->m_currentFindSupport)
         d->m_currentFindSupport->clearHighlights();
@@ -336,11 +344,8 @@ void BaseFileFind::openEditor(const SearchResultItem &item)
         return;
     // highlight results
     if (IFindSupport *findSupport = Aggregation::query<IFindSupport>(openedEditor->widget())) {
-        if (result) {
-            FileFindParameters parameters = result->userData().value<FileFindParameters>();
-            d->m_currentFindSupport = findSupport;
-            d->m_currentFindSupport->highlightAll(parameters.text, parameters.flags);
-        }
+        d->m_currentFindSupport = findSupport;
+        d->m_currentFindSupport->highlightAll(parameters.text, parameters.flags);
     }
 }
 
diff --git a/src/plugins/texteditor/basefilefind.h b/src/plugins/texteditor/basefilefind.h
index f21f946b91..a39d1f3643 100644
--- a/src/plugins/texteditor/basefilefind.h
+++ b/src/plugins/texteditor/basefilefind.h
@@ -41,9 +41,10 @@ QT_END_NAMESPACE
 
 namespace Utils { class FileIterator; }
 namespace Core {
+class IEditor;
+class IFindSupport;
 class SearchResult;
 class SearchResultItem;
-class IFindSupport;
 } // namespace Core
 
 namespace TextEditor {
@@ -65,6 +66,7 @@ class TEXTEDITOR_EXPORT FileFindExtension : public QObject
 public:
     virtual ~FileFindExtension() {}
     virtual QString title() const = 0;
+    virtual QString toolTip() const = 0; // add %1 placeholder where the find flags should be put
     virtual QWidget *widget() const = 0;
     virtual bool isEnabled() const = 0;
     virtual bool isEnabled(const FileFindParameters &parameters) const = 0;
@@ -73,6 +75,8 @@ public:
     virtual void writeSettings(QSettings *settings) const = 0;
     virtual QFuture<Utils::FileSearchResultList> executeSearch(
             const FileFindParameters &parameters) = 0;
+    virtual Core::IEditor *openEditor(const Core::SearchResultItem &item,
+                                      const FileFindParameters &parameters) = 0;
 };
 
 class TEXTEDITOR_EXPORT BaseFileFind : public Core::IFindFilter
-- 
GitLab