basefilefind.cpp 16.9 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** 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 Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "basefilefind.h"
31
#include "basefilefind_p.h"
con's avatar
con committed
32

33
#include <aggregation/aggregate.h>
34
#include <coreplugin/icore.h>
35
#include <coreplugin/progressmanager/progressmanager.h>
36
#include <coreplugin/progressmanager/futureprogress.h>
37
#include <coreplugin/dialogs/readonlyfilesdialog.h>
38
#include <coreplugin/documentmanager.h>
con's avatar
con committed
39
#include <texteditor/basetexteditor.h>
40
#include <texteditor/refactoringchanges.h>
41
#include <utils/stylehelper.h>
42
#include <utils/qtcassert.h>
43
#include <utils/filesearch.h>
con's avatar
con committed
44

45
46
47
48
#include <QDebug>
#include <QSettings>
#include <QHash>
#include <QPair>
49
50
51
#include <QStringListModel>
#include <QFutureWatcher>
#include <QPointer>
52
#include <QComboBox>
53
#include <QLabel>
54
#include <QLabel>
con's avatar
con committed
55

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
namespace TextEditor {
namespace Internal {
class BaseFileFindPrivate {
public:
    BaseFileFindPrivate() : m_resultLabel(0), m_filterCombo(0) {}

    QMap<QFutureWatcher<Utils::FileSearchResultList> *, QPointer<Find::SearchResult> > m_watchers;
    QPointer<Find::IFindSupport> m_currentFindSupport;

    QLabel *m_resultLabel;
    QStringListModel m_filterStrings;
    QString m_filterSetting;
    QPointer<QComboBox> m_filterCombo;
};
} // namespace Internal
} // namespace TextEditor

73
using namespace Utils;
con's avatar
con committed
74
75
using namespace Find;
using namespace TextEditor;
76
using namespace TextEditor::Internal;
con's avatar
con committed
77

78
79

BaseFileFind::BaseFileFind() : d(new BaseFileFindPrivate)
con's avatar
con committed
80
81
82
{
}

83
84
BaseFileFind::~BaseFileFind()
{
85
    delete d;
86
87
}

con's avatar
con committed
88
89
bool BaseFileFind::isEnabled() const
{
90
    return true;
con's avatar
con committed
91
92
}

dt's avatar
dt committed
93
94
void BaseFileFind::cancel()
{
95
96
    SearchResult *search = qobject_cast<SearchResult *>(sender());
    QTC_ASSERT(search, return);
97
    QFutureWatcher<FileSearchResultList> *watcher = d->m_watchers.key(search);
98
99
    QTC_ASSERT(watcher, return);
    watcher->cancel();
dt's avatar
dt committed
100
101
}

102
103
104
105
void BaseFileFind::setPaused(bool paused)
{
    SearchResult *search = qobject_cast<SearchResult *>(sender());
    QTC_ASSERT(search, return);
106
    QFutureWatcher<FileSearchResultList> *watcher = d->m_watchers.key(search);
107
108
109
110
111
    QTC_ASSERT(watcher, return);
    if (!paused || watcher->isRunning()) // guard against pausing when the search is finished
        watcher->setPaused(paused);
}

con's avatar
con committed
112
113
114
QStringList BaseFileFind::fileNameFilters() const
{
    QStringList filters;
115
116
    if (d->m_filterCombo && !d->m_filterCombo->currentText().isEmpty()) {
        const QStringList parts = d->m_filterCombo->currentText().split(QLatin1Char(','));
con's avatar
con committed
117
        foreach (const QString &part, parts) {
118
            const QString filter = part.trimmed();
119
            if (!filter.isEmpty())
con's avatar
con committed
120
121
122
123
124
125
                filters << filter;
        }
    }
    return filters;
}

126
127
void BaseFileFind::runNewSearch(const QString &txt, Find::FindFlags findFlags,
                                    SearchResultWindow::SearchMode searchMode)
con's avatar
con committed
128
{
129
130
131
    d->m_currentFindSupport = 0;
    if (d->m_filterCombo)
        updateComboEntries(d->m_filterCombo, true);
132
    SearchResult *search = Find::SearchResultWindow::instance()->startNewSearch(label(),
133
134
                           toolTip().arg(Find::IFindFilter::descriptionForFindFlags(findFlags)),
                           txt, searchMode, QString::fromLatin1("TextEditor"));
135
    search->setTextToReplace(txt);
136
137
138
139
140
141
142
    search->setSearchAgainSupported(true);
    FileFindParameters parameters;
    parameters.text = txt;
    parameters.flags = findFlags;
    parameters.nameFilters = fileNameFilters();
    parameters.additionalParameters = additionalParameters();
    search->setUserData(qVariantFromValue(parameters));
143
    connect(search, SIGNAL(activated(Find::SearchResultItem)), this, SLOT(openEditor(Find::SearchResultItem)));
144
    if (searchMode == SearchResultWindow::SearchAndReplace) {
145
146
        connect(search, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>,bool)),
                this, SLOT(doReplace(QString,QList<Find::SearchResultItem>,bool)));
147
    }
148
    connect(search, SIGNAL(visibilityChanged(bool)), this, SLOT(hideHighlightAll(bool)));
149
    connect(search, SIGNAL(cancelled()), this, SLOT(cancel()));
150
    connect(search, SIGNAL(paused(bool)), this, SLOT(setPaused(bool)));
151
    connect(search, SIGNAL(searchAgainRequested()), this, SLOT(searchAgain()));
152
153
154
    connect(this, SIGNAL(enabledChanged(bool)), search, SIGNAL(requestEnabledCheck()));
    connect(search, SIGNAL(requestEnabledCheck()), this, SLOT(recheckEnabled()));

155
156
157
158
159
160
    runSearch(search);
}

void BaseFileFind::runSearch(Find::SearchResult *search)
{
    FileFindParameters parameters = search->userData().value<FileFindParameters>();
161
162
    CountingLabel *label = new CountingLabel;
    connect(search, SIGNAL(countChanged(int)), label, SLOT(updateCount(int)));
163
164
    CountingLabel *statusLabel = new CountingLabel;
    connect(search, SIGNAL(countChanged(int)), statusLabel, SLOT(updateCount(int)));
165
    Find::SearchResultWindow::instance()->popup(Core::IOutputPane::Flags(Core::IOutputPane::ModeSwitch | Core::IOutputPane::WithFocus));
166
    QFutureWatcher<FileSearchResultList> *watcher = new QFutureWatcher<FileSearchResultList>();
167
    d->m_watchers.insert(watcher, search);
168
169
170
171
172
173
174
175
    watcher->setPendingResultsLimit(1);
    connect(watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int)));
    connect(watcher, SIGNAL(finished()), this, SLOT(searchFinished()));
    if (parameters.flags & Find::FindRegularExpression) {
        watcher->setFuture(Utils::findInFilesRegExp(parameters.text,
            files(parameters.nameFilters, parameters.additionalParameters),
            textDocumentFlagsForFindFlags(parameters.flags),
            ITextEditor::openedTextEditorsContents()));
176
    } else {
177
178
179
180
        watcher->setFuture(Utils::findInFiles(parameters.text,
            files(parameters.nameFilters, parameters.additionalParameters),
            textDocumentFlagsForFindFlags(parameters.flags),
            ITextEditor::openedTextEditorsContents()));
181
    }
182
    Core::FutureProgress *progress =
hjk's avatar
hjk committed
183
        Core::ICore::progressManager()->addTask(watcher->future(),
Robert Loehning's avatar
Robert Loehning committed
184
                                                                        tr("Search"),
185
                                                                        QLatin1String(Constants::TASK_SEARCH));
186
    progress->setWidget(label);
187
    progress->setStatusBarWidget(statusLabel);
188
    connect(progress, SIGNAL(clicked()), search, SLOT(popup()));
con's avatar
con committed
189
190
}

191
192
193
194
195
void BaseFileFind::findAll(const QString &txt, Find::FindFlags findFlags)
{
    runNewSearch(txt, findFlags, SearchResultWindow::SearchOnly);
}

196
void BaseFileFind::replaceAll(const QString &txt, Find::FindFlags findFlags)
197
{
198
    runNewSearch(txt, findFlags, SearchResultWindow::SearchAndReplace);
199
200
201
}

void BaseFileFind::doReplace(const QString &text,
202
203
                             const QList<Find::SearchResultItem> &items,
                             bool preserveCase)
204
{
205
    QStringList files = replaceAll(text, items, preserveCase);
206
    if (!files.isEmpty()) {
207
        Core::DocumentManager::notifyFilesChangedInternally(files);
208
        Find::SearchResultWindow::instance()->hide();
209
210
211
    }
}

con's avatar
con committed
212
void BaseFileFind::displayResult(int index) {
213
214
    QFutureWatcher<FileSearchResultList> *watcher =
            static_cast<QFutureWatcher<FileSearchResultList> *>(sender());
215
    SearchResult *search = d->m_watchers.value(watcher);
216
217
218
    if (!search) {
        // search was removed from search history while the search is running
        watcher->cancel();
219
220
        return;
    }
221
    Utils::FileSearchResultList results = watcher->resultAt(index);
222
    QList<Find::SearchResultItem> items;
223
224
    foreach (const Utils::FileSearchResult &result, results) {
        Find::SearchResultItem item;
225
        item.path = QStringList() << QDir::toNativeSeparators(result.fileName);
226
        item.lineNumber = result.lineNumber;
227
228
229
230
        item.text = result.matchingLine;
        item.textMarkLength = result.matchLength;
        item.textMarkPos = result.matchStart;
        item.useTextEditorFont = true;
231
232
233
        item.userData = result.regexpCapturedTexts;
        items << item;
    }
234
    search->addResults(items, Find::SearchResult::AddOrdered);
con's avatar
con committed
235
236
237
238
}

void BaseFileFind::searchFinished()
{
239
240
    QFutureWatcher<FileSearchResultList> *watcher =
            static_cast<QFutureWatcher<FileSearchResultList> *>(sender());
241
    SearchResult *search = d->m_watchers.value(watcher);
242
    if (search)
Eike Ziller's avatar
Eike Ziller committed
243
        search->finishSearch(watcher->isCanceled());
244
    d->m_watchers.remove(watcher);
245
    watcher->deleteLater();
con's avatar
con committed
246
247
248
249
250
}

QWidget *BaseFileFind::createPatternWidget()
{
    QString filterToolTip = tr("List of comma separated wildcard filters");
251
252
253
254
255
256
257
258
259
260
261
    d->m_filterCombo = new QComboBox;
    d->m_filterCombo->setEditable(true);
    d->m_filterCombo->setModel(&d->m_filterStrings);
    d->m_filterCombo->setMaxCount(10);
    d->m_filterCombo->setMinimumContentsLength(10);
    d->m_filterCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
    d->m_filterCombo->setInsertPolicy(QComboBox::InsertAtBottom);
    d->m_filterCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    d->m_filterCombo->setToolTip(filterToolTip);
    syncComboWithSettings(d->m_filterCombo, d->m_filterSetting);
    return d->m_filterCombo;
con's avatar
con committed
262
263
264
265
}

void BaseFileFind::writeCommonSettings(QSettings *settings)
{
266
267
268
    settings->setValue(QLatin1String("filters"), d->m_filterStrings.stringList());
    if (d->m_filterCombo)
        settings->setValue(QLatin1String("currentFilter"), d->m_filterCombo->currentText());
con's avatar
con committed
269
270
271
272
}

void BaseFileFind::readCommonSettings(QSettings *settings, const QString &defaultFilter)
{
273
    QStringList filters = settings->value(QLatin1String("filters")).toStringList();
274
    d->m_filterSetting = settings->value(QLatin1String("currentFilter")).toString();
con's avatar
con committed
275
276
    if (filters.isEmpty())
        filters << defaultFilter;
277
278
279
280
281
    if (d->m_filterSetting.isEmpty())
        d->m_filterSetting = filters.first();
    d->m_filterStrings.setStringList(filters);
    if (d->m_filterCombo)
        syncComboWithSettings(d->m_filterCombo, d->m_filterSetting);
con's avatar
con committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
}

void BaseFileFind::syncComboWithSettings(QComboBox *combo, const QString &setting)
{
    if (!combo)
        return;
    int index = combo->findText(setting);
    if (index < 0)
        combo->setEditText(setting);
    else
        combo->setCurrentIndex(index);
}

void BaseFileFind::updateComboEntries(QComboBox *combo, bool onTop)
{
    int index = combo->findText(combo->currentText());
    if (index < 0) {
299
        if (onTop)
con's avatar
con committed
300
            combo->insertItem(0, combo->currentText());
301
        else
con's avatar
con committed
302
303
304
305
306
            combo->addItem(combo->currentText());
        combo->setCurrentIndex(combo->findText(combo->currentText()));
    }
}

307
void BaseFileFind::openEditor(const Find::SearchResultItem &item)
con's avatar
con committed
308
{
309
310
    SearchResult *result = qobject_cast<SearchResult *>(sender());
    Core::IEditor *openedEditor = 0;
311
    if (item.path.size() > 0) {
312
313
314
        openedEditor = TextEditor::BaseTextEditorWidget::openEditorAt(QDir::fromNativeSeparators(item.path.first()),
                                                                      item.lineNumber,
                                                                      item.textMarkPos,
hjk's avatar
hjk committed
315
                                                                      Core::Id(),
316
                                                                      Core::EditorManager::ModeSwitch);
317
    } else {
hjk's avatar
hjk committed
318
319
        openedEditor = Core::EditorManager::openEditor(QDir::fromNativeSeparators(item.text),
                                                        Core::Id(), Core::EditorManager::ModeSwitch);
320
    }
321
322
323
    if (d->m_currentFindSupport)
        d->m_currentFindSupport->clearResults();
    d->m_currentFindSupport = 0;
324
325
326
327
328
    if (!openedEditor)
        return;
    // highlight results
    if (IFindSupport *findSupport = Aggregation::query<IFindSupport>(openedEditor->widget())) {
        if (result) {
329
            FileFindParameters parameters = result->userData().value<FileFindParameters>();
330
331
            d->m_currentFindSupport = findSupport;
            d->m_currentFindSupport->highlightAll(parameters.text, parameters.flags);
332
        }
333
    }
con's avatar
con committed
334
}
335

336
337
void BaseFileFind::hideHighlightAll(bool visible)
{
338
339
    if (!visible && d->m_currentFindSupport)
        d->m_currentFindSupport->clearResults();
340
341
}

342
343
344
void BaseFileFind::searchAgain()
{
    SearchResult *search = qobject_cast<SearchResult *>(sender());
Eike Ziller's avatar
Eike Ziller committed
345
    search->restart();
346
347
348
    runSearch(search);
}

349
350
351
352
353
354
355
356
void BaseFileFind::recheckEnabled()
{
    SearchResult *search = qobject_cast<SearchResult *>(sender());
    if (!search)
        return;
    search->setSearchAgainEnabled(isEnabled());
}

357
QStringList BaseFileFind::replaceAll(const QString &text,
358
359
                                     const QList<Find::SearchResultItem> &items,
                                     bool preserveCase)
360
{
361
    if (items.isEmpty())
362
363
        return QStringList();

364
    RefactoringChanges refactoring;
365

366
    QHash<QString, QList<Find::SearchResultItem> > changes;
367
    foreach (const Find::SearchResultItem &item, items)
368
        changes[QDir::fromNativeSeparators(item.path.first())].append(item);
369

370
    // Checking for files without write permissions
371
    QHashIterator<QString, QList<Find::SearchResultItem> > it(changes);
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
    QSet<QString> roFiles;
    while (it.hasNext()) {
        it.next();
        const QFileInfo fileInfo(it.key());
        if (!fileInfo.isWritable())
            roFiles.insert(it.key());
    }

    // Query the user for permissions
    if (!roFiles.isEmpty()) {
        Core::Internal::ReadOnlyFilesDialog roDialog(roFiles.toList(),
                                                     Core::ICore::instance()->mainWindow());
        roDialog.setShowFailWarning(true, tr("Aborting replace."));
        if (roDialog.exec() == Core::Internal::ReadOnlyFilesDialog::RO_Cancel)
            return QStringList();
    }

    it.toFront();
390
391
392
    while (it.hasNext()) {
        it.next();
        const QString fileName = it.key();
393
        const QList<Find::SearchResultItem> changeItems = it.value();
394

395
396
397
398
399
400
401
402
403
404
        ChangeSet changeSet;
        RefactoringFilePtr file = refactoring.file(fileName);
        QSet<QPair<int, int> > processed;
        foreach (const Find::SearchResultItem &item, changeItems) {
            const QPair<int, int> &p = qMakePair(item.lineNumber, item.textMarkPos);
            if (processed.contains(p))
                continue;
            processed.insert(p);

            QString replacement;
405
            if (item.userData.canConvert<QStringList>() && !item.userData.toStringList().isEmpty()) {
406
                replacement = Utils::expandRegExpReplacement(text, item.userData.toStringList());
407
408
409
410
411
            } else if (preserveCase) {
                const QString originalText = (item.textMarkLength == 0) ? item.text
                                                                        : item.text.mid(item.textMarkPos, item.textMarkLength);
                replacement = Utils::matchCaseReplacement(originalText, text);
            } else {
412
                replacement = text;
413
            }
414
415
416
417
418

            const int start = file->position(item.lineNumber, item.textMarkPos + 1);
            const int end = file->position(item.lineNumber,
                                           item.textMarkPos + item.textMarkLength + 1);
            changeSet.replace(start, end, replacement);
419
        }
420
421
        file->setChangeSet(changeSet);
        file->apply();
422
423
424
425
    }

    return changes.keys();
}
426

427
428
429
430
431
QVariant BaseFileFind::getAdditionalParameters(SearchResult *search)
{
    return search->userData().value<FileFindParameters>().additionalParameters;
}

432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
CountingLabel::CountingLabel()
{
    setAlignment(Qt::AlignCenter);
    // ### TODO this setup should be done by style
    QFont f = font();
    f.setBold(true);
    f.setPointSizeF(StyleHelper::sidebarFontSize());
    setFont(f);
    setPalette(StyleHelper::sidebarFontPalette(palette()));
    updateCount(0);
}

void CountingLabel::updateCount(int count)
{
    setText(tr("%1 found").arg(count));
}