basefilefind.cpp 13.2 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
con's avatar
con committed
30
**
31
**************************************************************************/
hjk's avatar
hjk committed
32

con's avatar
con committed
33
34
#include "basefilefind.h"

35
#include <aggregation/aggregate.h>
36
#include <coreplugin/icore.h>
37
#include <coreplugin/progressmanager/progressmanager.h>
38
#include <coreplugin/progressmanager/futureprogress.h>
con's avatar
con committed
39
#include <coreplugin/editormanager/editormanager.h>
40
#include <coreplugin/editormanager/ieditor.h>
41
#include <coreplugin/filemanager.h>
con's avatar
con committed
42
43
44
#include <find/textfindconstants.h>
#include <texteditor/itexteditor.h>
#include <texteditor/basetexteditor.h>
45
#include <texteditor/refactoringchanges.h>
46
#include <utils/stylehelper.h>
47
#include <utils/fileutils.h>
48
#include <utils/qtcassert.h>
con's avatar
con committed
49

50
#include <QtCore/QDebug>
con's avatar
con committed
51
#include <QtCore/QDirIterator>
52
#include <QtCore/QSettings>
53
54
#include <QtCore/QHash>
#include <QtCore/QPair>
55
#include <QtGui/QFileDialog>
56
#include <QtGui/QCheckBox>
57
#include <QtGui/QComboBox>
58
#include <QtGui/QHBoxLayout>
59
#include <QtGui/QLabel>
60
#include <QtGui/QMainWindow>
con's avatar
con committed
61
#include <QtGui/QPushButton>
62
#include <QtGui/QTextBlock>
con's avatar
con committed
63

64
using namespace Utils;
con's avatar
con committed
65
66
67
using namespace Find;
using namespace TextEditor;

68
69
70
BaseFileFind::BaseFileFind()
  : m_currentSearch(0),
    m_currentSearchCount(0),
71
    m_watcher(0),
con's avatar
con committed
72
73
    m_isSearching(false),
    m_resultLabel(0),
74
    m_filterCombo(0)
con's avatar
con committed
75
76
77
{
}

78
79
80
81
BaseFileFind::~BaseFileFind()
{
}

con's avatar
con committed
82
83
84
85
86
bool BaseFileFind::isEnabled() const
{
    return !m_isSearching;
}

dt's avatar
dt committed
87
88
void BaseFileFind::cancel()
{
89
90
    QTC_ASSERT(m_watcher, return);
    m_watcher->cancel();
dt's avatar
dt committed
91
92
}

con's avatar
con committed
93
94
95
96
QStringList BaseFileFind::fileNameFilters() const
{
    QStringList filters;
    if (m_filterCombo && !m_filterCombo->currentText().isEmpty()) {
97
        const QStringList parts = m_filterCombo->currentText().split(QLatin1Char(','));
con's avatar
con committed
98
        foreach (const QString &part, parts) {
99
            const QString filter = part.trimmed();
con's avatar
con committed
100
101
102
103
104
105
106
107
            if (!filter.isEmpty()) {
                filters << filter;
            }
        }
    }
    return filters;
}

108
109
void BaseFileFind::runNewSearch(const QString &txt, Find::FindFlags findFlags,
                                    SearchResultWindow::SearchMode searchMode)
con's avatar
con committed
110
111
{
    m_isSearching = true;
112
    m_currentFindSupport = 0;
con's avatar
con committed
113
    emit changed();
con's avatar
con committed
114
    if (m_filterCombo)
115
        updateComboEntries(m_filterCombo, true);
116
117
118
119
120
    delete m_watcher;
    m_watcher = new QFutureWatcher<FileSearchResultList>();
    m_watcher->setPendingResultsLimit(1);
    connect(m_watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int)));
    connect(m_watcher, SIGNAL(finished()), this, SLOT(searchFinished()));
121
    m_currentSearchCount = 0;
122
123
124
    m_currentSearch = Find::SearchResultWindow::instance()->startNewSearch(label(),
                           toolTip().arg(Find::IFindFilter::descriptionForFindFlags(findFlags)),
                           txt, searchMode, QString::fromLatin1("TextEditor"));
125
    m_currentSearch->setTextToReplace(txt);
126
127
    QVariantList searchParameters;
    searchParameters << qVariantFromValue(txt) << qVariantFromValue(findFlags);
128
129
    m_currentSearch->setUserData(searchParameters);
    connect(m_currentSearch, SIGNAL(activated(Find::SearchResultItem)), this, SLOT(openEditor(Find::SearchResultItem)));
130
    if (searchMode == SearchResultWindow::SearchAndReplace) {
131
        connect(m_currentSearch, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
132
133
                this, SLOT(doReplace(QString,QList<Find::SearchResultItem>)));
    }
134
135
    connect(m_currentSearch, SIGNAL(visibilityChanged(bool)), this, SLOT(hideHighlightAll(bool)));
    Find::SearchResultWindow::instance()->popup(true);
136
    if (findFlags & Find::FindRegularExpression) {
137
        m_watcher->setFuture(Utils::findInFilesRegExp(txt, files(),
138
139
            textDocumentFlagsForFindFlags(findFlags), ITextEditor::openedTextEditorsContents()));
    } else {
140
        m_watcher->setFuture(Utils::findInFiles(txt, files(),
141
142
            textDocumentFlagsForFindFlags(findFlags), ITextEditor::openedTextEditorsContents()));
    }
143
    connect(m_currentSearch, SIGNAL(cancelled()), this, SLOT(cancel()));
144
    Core::FutureProgress *progress =
145
        Core::ICore::instance()->progressManager()->addTask(m_watcher->future(),
Robert Loehning's avatar
Robert Loehning committed
146
                                                                        tr("Search"),
147
                                                                        Constants::TASK_SEARCH);
con's avatar
con committed
148
    progress->setWidget(createProgressWidget());
149
    connect(progress, SIGNAL(clicked()), Find::SearchResultWindow::instance(), SLOT(popup()));
con's avatar
con committed
150
151
}

152
153
154
155
156
void BaseFileFind::findAll(const QString &txt, Find::FindFlags findFlags)
{
    runNewSearch(txt, findFlags, SearchResultWindow::SearchOnly);
}

157
void BaseFileFind::replaceAll(const QString &txt, Find::FindFlags findFlags)
158
{
159
    runNewSearch(txt, findFlags, SearchResultWindow::SearchAndReplace);
160
161
162
163
164
165
166
167
168
}

void BaseFileFind::doReplace(const QString &text,
                               const QList<Find::SearchResultItem> &items)
{
    QStringList files = replaceAll(text, items);
    Core::FileManager *fileManager = Core::ICore::instance()->fileManager();
    if (!files.isEmpty()) {
        fileManager->notifyFilesChangedInternally(files);
169
        Find::SearchResultWindow::instance()->hide();
170
171
172
    }
}

con's avatar
con committed
173
void BaseFileFind::displayResult(int index) {
174
    if (!m_currentSearch) {
175
        m_watcher->cancel();
176
177
        return;
    }
178
    Utils::FileSearchResultList results = m_watcher->resultAt(index);
179
    QList<Find::SearchResultItem> items;
180
181
    foreach (const Utils::FileSearchResult &result, results) {
        Find::SearchResultItem item;
182
        item.path = QStringList() << QDir::toNativeSeparators(result.fileName);
183
        item.lineNumber = result.lineNumber;
184
185
186
187
        item.text = result.matchingLine;
        item.textMarkLength = result.matchLength;
        item.textMarkPos = result.matchStart;
        item.useTextEditorFont = true;
188
189
190
        item.userData = result.regexpCapturedTexts;
        items << item;
    }
191
192
    m_currentSearch->addResults(items, Find::SearchResult::AddOrdered);
    m_currentSearchCount += items.count();
con's avatar
con committed
193
    if (m_resultLabel)
194
        m_resultLabel->setText(tr("%1 found").arg(m_currentSearchCount));
con's avatar
con committed
195
196
197
198
}

void BaseFileFind::searchFinished()
{
199
200
    if (m_currentSearch)
        m_currentSearch->finishSearch();
201
    m_currentSearch = 0;
con's avatar
con committed
202
203
    m_isSearching = false;
    m_resultLabel = 0;
204
205
    m_watcher->deleteLater();
    m_watcher = 0;
con's avatar
con committed
206
207
208
209
210
211
    emit changed();
}

QWidget *BaseFileFind::createProgressWidget()
{
    m_resultLabel = new QLabel;
212
    m_resultLabel->setAlignment(Qt::AlignCenter);
con's avatar
con committed
213
214
215
216
217
218
    // ### TODO this setup should be done by style
    QFont f = m_resultLabel->font();
    f.setBold(true);
    f.setPointSizeF(StyleHelper::sidebarFontSize());
    m_resultLabel->setFont(f);
    m_resultLabel->setPalette(StyleHelper::sidebarFontPalette(m_resultLabel->palette()));
219
    m_resultLabel->setText(tr("%1 found").arg(m_currentSearchCount));
con's avatar
con committed
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
    return m_resultLabel;
}

QWidget *BaseFileFind::createPatternWidget()
{
    QString filterToolTip = tr("List of comma separated wildcard filters");
    m_filterCombo = new QComboBox;
    m_filterCombo->setEditable(true);
    m_filterCombo->setModel(&m_filterStrings);
    m_filterCombo->setMaxCount(10);
    m_filterCombo->setMinimumContentsLength(10);
    m_filterCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
    m_filterCombo->setInsertPolicy(QComboBox::InsertAtBottom);
    m_filterCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    m_filterCombo->setToolTip(filterToolTip);
    syncComboWithSettings(m_filterCombo, m_filterSetting);
    return m_filterCombo;
}

void BaseFileFind::writeCommonSettings(QSettings *settings)
{
    settings->setValue("filters", m_filterStrings.stringList());
    if (m_filterCombo)
        settings->setValue("currentFilter", m_filterCombo->currentText());
}

void BaseFileFind::readCommonSettings(QSettings *settings, const QString &defaultFilter)
{
    QStringList filters = settings->value("filters").toStringList();
    m_filterSetting = settings->value("currentFilter").toString();
    if (filters.isEmpty())
        filters << defaultFilter;
    if (m_filterSetting.isEmpty())
        m_filterSetting = filters.first();
    m_filterStrings.setStringList(filters);
con's avatar
con committed
255
256
    if (m_filterCombo)
        syncComboWithSettings(m_filterCombo, m_filterSetting);
con's avatar
con committed
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
}

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) {
        if (onTop) {
            combo->insertItem(0, combo->currentText());
        } else {
            combo->addItem(combo->currentText());
        }
        combo->setCurrentIndex(combo->findText(combo->currentText()));
    }
}

283
void BaseFileFind::openEditor(const Find::SearchResultItem &item)
con's avatar
con committed
284
{
285
286
    SearchResult *result = qobject_cast<SearchResult *>(sender());
    Core::IEditor *openedEditor = 0;
287
    if (item.path.size() > 0) {
288
289
290
        openedEditor = TextEditor::BaseTextEditorWidget::openEditorAt(QDir::fromNativeSeparators(item.path.first()),
                                                                      item.lineNumber,
                                                                      item.textMarkPos,
hjk's avatar
hjk committed
291
                                                                      Core::Id(),
292
                                                                      Core::EditorManager::ModeSwitch);
293
    } else {
hjk's avatar
hjk committed
294
        openedEditor = Core::EditorManager::instance()->openEditor(item.text, Core::Id(),
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
                                                                   Core::EditorManager::ModeSwitch);
    }
    if (m_currentFindSupport)
        m_currentFindSupport->clearResults();
    m_currentFindSupport = 0;
    if (!openedEditor)
        return;
    // highlight results
    if (IFindSupport *findSupport = Aggregation::query<IFindSupport>(openedEditor->widget())) {
        if (result) {
            QVariantList userData = result->userData().value<QVariantList>();
            QTC_ASSERT(userData.size() != 0, return);
            m_currentFindSupport = findSupport;
            m_currentFindSupport->highlightAll(userData.at(0).toString(), userData.at(1).value<FindFlags>());
        }
310
    }
con's avatar
con committed
311
}
312

313
314
315
316
317
318
void BaseFileFind::hideHighlightAll(bool visible)
{
    if (!visible && m_currentFindSupport)
        m_currentFindSupport->clearResults();
}

319
320
321
QStringList BaseFileFind::replaceAll(const QString &text,
                               const QList<Find::SearchResultItem> &items)
{
322
    if (items.isEmpty())
323
324
        return QStringList();

325
    RefactoringChanges refactoring;
326

327
    QHash<QString, QList<Find::SearchResultItem> > changes;
328
    foreach (const Find::SearchResultItem &item, items)
329
        changes[QDir::fromNativeSeparators(item.path.first())].append(item);
330
331
332
333
334

    QHashIterator<QString, QList<Find::SearchResultItem> > it(changes);
    while (it.hasNext()) {
        it.next();
        const QString fileName = it.key();
335
        const QList<Find::SearchResultItem> changeItems = it.value();
336

337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
        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;
            if (item.userData.canConvert<QStringList>() && !item.userData.toStringList().isEmpty())
                replacement = Utils::expandRegExpReplacement(text, item.userData.toStringList());
            else
                replacement = text;

            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);
356
        }
357
358
        file->setChangeSet(changeSet);
        file->apply();
359
360
361
362
    }

    return changes.keys();
}