gitgrep.cpp 8.08 KB
Newer Older
Orgad Shaneh's avatar
Orgad Shaneh committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/****************************************************************************
**
** Copyright (C) 2016 Orgad Shaneh <orgads@gmail.com>.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/

#include "gitgrep.h"
#include "gitclient.h"
#include "gitplugin.h"

30
#include <coreplugin/progressmanager/progressmanager.h>
Orgad Shaneh's avatar
Orgad Shaneh committed
31
#include <coreplugin/vcsmanager.h>
32
#include <texteditor/findinfiles.h>
Orgad Shaneh's avatar
Orgad Shaneh committed
33
#include <vcsbase/vcscommand.h>
Orgad Shaneh's avatar
Orgad Shaneh committed
34
35
36
37
38
39
#include <vcsbase/vcsbaseconstants.h>

#include <utils/filesearch.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
Orgad Shaneh's avatar
Orgad Shaneh committed
40
#include <utils/synchronousprocess.h>
Orgad Shaneh's avatar
Orgad Shaneh committed
41

42
#include <QCheckBox>
Orgad Shaneh's avatar
Orgad Shaneh committed
43
44
#include <QFuture>
#include <QFutureWatcher>
Orgad Shaneh's avatar
Orgad Shaneh committed
45
#include <QScopedPointer>
Orgad Shaneh's avatar
Orgad Shaneh committed
46
#include <QSettings>
Orgad Shaneh's avatar
Orgad Shaneh committed
47
#include <QTextStream>
Orgad Shaneh's avatar
Orgad Shaneh committed
48
49
50
51
52

namespace Git {
namespace Internal {

using namespace Core;
Orgad Shaneh's avatar
Orgad Shaneh committed
53
using namespace Utils;
Orgad Shaneh's avatar
Orgad Shaneh committed
54
using VcsBase::VcsCommand;
Orgad Shaneh's avatar
Orgad Shaneh committed
55
56
57

namespace {

58
59
const char EnableGitGrep[] = "EnableGitGrep";

Orgad Shaneh's avatar
Orgad Shaneh committed
60
61
62
63
64
65
66
67
68
69
70
71
72
class GitGrepRunner : public QObject
{
    using FutureInterfaceType = QFutureInterface<FileSearchResultList>;

public:
    GitGrepRunner(FutureInterfaceType &fi,
                  const TextEditor::FileFindParameters &parameters) :
        m_fi(fi),
        m_parameters(parameters)
    {
        m_directory = parameters.additionalParameters.toString();
    }

Orgad Shaneh's avatar
Orgad Shaneh committed
73
    void processLine(const QString &line, FileSearchResultList *resultList) const
Orgad Shaneh's avatar
Orgad Shaneh committed
74
75
76
    {
        if (line.isEmpty())
            return;
Orgad Shaneh's avatar
Orgad Shaneh committed
77
78
        static const QLatin1String boldRed("\x1b[1;31m");
        static const QLatin1String resetColor("\x1b[m");
Orgad Shaneh's avatar
Orgad Shaneh committed
79
        FileSearchResult single;
Orgad Shaneh's avatar
Orgad Shaneh committed
80
        const int lineSeparator = line.indexOf(QChar::Null);
Orgad Shaneh's avatar
Orgad Shaneh committed
81
        single.fileName = m_directory + QLatin1Char('/')
Orgad Shaneh's avatar
Orgad Shaneh committed
82
83
                + line.left(lineSeparator);
        const int textSeparator = line.indexOf(QChar::Null, lineSeparator + 1);
Orgad Shaneh's avatar
Orgad Shaneh committed
84
        single.lineNumber = line.mid(lineSeparator + 1, textSeparator - lineSeparator - 1).toInt();
Orgad Shaneh's avatar
Orgad Shaneh committed
85
        QString text = line.mid(textSeparator + 1);
Orgad Shaneh's avatar
Orgad Shaneh committed
86
87
88
89
90
        QVector<QPair<int, int>> matches;
        for (;;) {
            const int matchStart = text.indexOf(boldRed);
            if (matchStart == -1)
                break;
Orgad Shaneh's avatar
Orgad Shaneh committed
91
            const int matchTextStart = matchStart + boldRed.size();
Orgad Shaneh's avatar
Orgad Shaneh committed
92
93
94
95
96
            const int matchEnd = text.indexOf(resetColor, matchTextStart);
            QTC_ASSERT(matchEnd != -1, break);
            const int matchLength = matchEnd - matchTextStart;
            matches.append(qMakePair(matchStart, matchLength));
            text = text.left(matchStart) + text.mid(matchTextStart, matchLength)
Orgad Shaneh's avatar
Orgad Shaneh committed
97
                    + text.mid(matchEnd + resetColor.size());
Orgad Shaneh's avatar
Orgad Shaneh committed
98
        }
Orgad Shaneh's avatar
Orgad Shaneh committed
99
        single.matchingLine = text;
Orgad Shaneh's avatar
Orgad Shaneh committed
100
101
102
103
104
105
106
        foreach (auto match, matches) {
            single.matchStart = match.first;
            single.matchLength = match.second;
            resultList->append(single);
        }
    }

Orgad Shaneh's avatar
Orgad Shaneh committed
107
    void read(const QString &text)
Orgad Shaneh's avatar
Orgad Shaneh committed
108
109
    {
        FileSearchResultList resultList;
Orgad Shaneh's avatar
Orgad Shaneh committed
110
111
112
113
        QString t = text;
        QTextStream stream(&t);
        while (!stream.atEnd() && !m_fi.isCanceled())
            processLine(stream.readLine(), &resultList);
Orgad Shaneh's avatar
Orgad Shaneh committed
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
        if (!resultList.isEmpty())
            m_fi.reportResult(resultList);
    }

    void exec()
    {
        QStringList arguments;
        arguments << QLatin1String("-c") << QLatin1String("color.grep.match=bold red")
                  << QLatin1String("grep") << QLatin1String("-zn")
                  << QLatin1String("--color=always");
        if (!(m_parameters.flags & FindCaseSensitively))
            arguments << QLatin1String("-i");
        if (m_parameters.flags & FindWholeWords)
            arguments << QLatin1String("-w");
        if (m_parameters.flags & FindRegularExpression)
            arguments << QLatin1String("-P");
        else
            arguments << QLatin1String("-F");
        arguments << m_parameters.text;
        arguments << QLatin1String("--") << m_parameters.nameFilters;
Orgad Shaneh's avatar
Orgad Shaneh committed
134
135
136
137
        GitClient *client = GitPlugin::instance()->client();
        QScopedPointer<VcsCommand> command(client->createCommand(m_directory));
        command->addFlags(VcsCommand::SilentOutput);
        command->setProgressiveOutput(true);
Orgad Shaneh's avatar
Orgad Shaneh committed
138
139
140
        QFutureWatcher<FileSearchResultList> watcher;
        watcher.setFuture(m_fi.future());
        connect(&watcher, &QFutureWatcher<FileSearchResultList>::canceled,
Orgad Shaneh's avatar
Orgad Shaneh committed
141
142
143
                command.data(), &VcsCommand::cancel);
        connect(command.data(), &VcsCommand::stdOutText, this, &GitGrepRunner::read);
        SynchronousProcessResponse resp = command->runCommand(client->vcsBinary(), arguments, 0);
144
145
146
147
        switch (resp.result) {
        case SynchronousProcessResponse::TerminatedAbnormally:
        case SynchronousProcessResponse::StartFailed:
        case SynchronousProcessResponse::Hang:
Orgad Shaneh's avatar
Orgad Shaneh committed
148
            m_fi.reportCanceled();
149
150
151
152
153
154
155
            break;
        case SynchronousProcessResponse::Finished:
        case SynchronousProcessResponse::FinishedError:
            // When no results are found, git-grep exits with non-zero status.
            // Do not consider this as an error.
            break;
        }
Orgad Shaneh's avatar
Orgad Shaneh committed
156
157
158
159
160
161
    }

    static void run(QFutureInterface<FileSearchResultList> &fi,
                    TextEditor::FileFindParameters parameters)
    {
        GitGrepRunner runner(fi, parameters);
162
        Core::ProgressTimer progress(fi, 20);
Orgad Shaneh's avatar
Orgad Shaneh committed
163
164
165
166
167
168
169
170
171
172
173
        runner.exec();
    }

private:
    FutureInterfaceType m_fi;
    QString m_directory;
    const TextEditor::FileFindParameters &m_parameters;
};

} // namespace

174
static bool validateDirectory(const QString &path)
Orgad Shaneh's avatar
Orgad Shaneh committed
175
{
176
177
178
    static IVersionControl *gitVc = VcsManager::versionControl(VcsBase::Constants::VCS_ID_GIT);
    QTC_ASSERT(gitVc, return false);
    return gitVc == VcsManager::findVersionControlForDirectory(path, 0);
Orgad Shaneh's avatar
Orgad Shaneh committed
179
180
}

181
GitGrep::GitGrep()
Orgad Shaneh's avatar
Orgad Shaneh committed
182
{
183
184
185
186
187
    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."));
    TextEditor::FindInFiles *findInFiles = TextEditor::FindInFiles::instance();
    QTC_ASSERT(findInFiles, return);
Orgad Shaneh's avatar
Orgad Shaneh committed
188
189
    connect(findInFiles, &TextEditor::FindInFiles::pathChanged,
            m_widget, [this](const QString &path) {
190
191
192
        m_widget->setEnabled(validateDirectory(path));
    });
    findInFiles->setFindExtension(this);
Orgad Shaneh's avatar
Orgad Shaneh committed
193
194
}

195
GitGrep::~GitGrep()
Orgad Shaneh's avatar
Orgad Shaneh committed
196
{
Orgad Shaneh's avatar
Orgad Shaneh committed
197
    delete m_widget;
Orgad Shaneh's avatar
Orgad Shaneh committed
198
199
}

200
QString GitGrep::title() const
Orgad Shaneh's avatar
Orgad Shaneh committed
201
{
202
    return tr("Git Grep");
Orgad Shaneh's avatar
Orgad Shaneh committed
203
204
}

205
QWidget *GitGrep::widget() const
Orgad Shaneh's avatar
Orgad Shaneh committed
206
{
Orgad Shaneh's avatar
Orgad Shaneh committed
207
    return m_widget;
Orgad Shaneh's avatar
Orgad Shaneh committed
208
209
}

210
bool GitGrep::isEnabled() const
Orgad Shaneh's avatar
Orgad Shaneh committed
211
{
212
    return m_widget->isEnabled() && m_widget->isChecked();
Orgad Shaneh's avatar
Orgad Shaneh committed
213
214
}

215
bool GitGrep::isEnabled(const TextEditor::FileFindParameters &parameters) const
Orgad Shaneh's avatar
Orgad Shaneh committed
216
{
217
    return parameters.extensionParameters.toBool();
Orgad Shaneh's avatar
Orgad Shaneh committed
218
219
}

220
QVariant GitGrep::parameters() const
Orgad Shaneh's avatar
Orgad Shaneh committed
221
{
222
    return isEnabled();
Orgad Shaneh's avatar
Orgad Shaneh committed
223
224
225
226
}

void GitGrep::readSettings(QSettings *settings)
{
227
    m_widget->setChecked(settings->value(QLatin1String(EnableGitGrep), false).toBool());
Orgad Shaneh's avatar
Orgad Shaneh committed
228
229
}

230
void GitGrep::writeSettings(QSettings *settings) const
Orgad Shaneh's avatar
Orgad Shaneh committed
231
{
232
    settings->setValue(QLatin1String(EnableGitGrep), m_widget->isChecked());
Orgad Shaneh's avatar
Orgad Shaneh committed
233
234
}

235
236
QFuture<FileSearchResultList> GitGrep::executeSearch(
        const TextEditor::FileFindParameters &parameters)
Orgad Shaneh's avatar
Orgad Shaneh committed
237
{
238
    return Utils::runAsync<FileSearchResultList>(GitGrepRunner::run, parameters);
Orgad Shaneh's avatar
Orgad Shaneh committed
239
240
241
242
}

} // Internal
} // Git