basefilefind.cpp 13.3 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 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
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** 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.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

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

32
#include <coreplugin/icore.h>
33
#include <coreplugin/progressmanager/progressmanager.h>
34
#include <coreplugin/progressmanager/futureprogress.h>
con's avatar
con committed
35
#include <coreplugin/editormanager/editormanager.h>
36
#include <coreplugin/filemanager.h>
con's avatar
con committed
37
#include <find/textfindconstants.h>
38
#include <find/searchresultwindow.h>
con's avatar
con committed
39
40
#include <texteditor/itexteditor.h>
#include <texteditor/basetexteditor.h>
41
#include <utils/stylehelper.h>
con's avatar
con committed
42

43
#include <QtCore/QDebug>
con's avatar
con committed
44
#include <QtCore/QDirIterator>
45
#include <QtCore/QSettings>
46
#include <QtGui/QFileDialog>
47
#include <QtGui/QCheckBox>
48
49
#include <QtGui/QComboBox>
#include <QtGui/QLabel>
con's avatar
con committed
50
#include <QtGui/QPushButton>
51
#include <QtGui/QTextBlock>
con's avatar
con committed
52

53
using namespace Utils;
con's avatar
con committed
54
55
56
using namespace Find;
using namespace TextEditor;

57
58
BaseFileFind::BaseFileFind(SearchResultWindow *resultWindow)
  : m_resultWindow(resultWindow),
con's avatar
con committed
59
60
    m_isSearching(false),
    m_resultLabel(0),
61
    m_filterCombo(0)
con's avatar
con committed
62
63
64
65
66
67
68
69
70
71
72
{
    m_watcher.setPendingResultsLimit(1);
    connect(&m_watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int)));
    connect(&m_watcher, SIGNAL(finished()), this, SLOT(searchFinished()));
}

bool BaseFileFind::isEnabled() const
{
    return !m_isSearching;
}

dt's avatar
dt committed
73
74
75
76
77
78
79
80
81
82
bool BaseFileFind::canCancel() const
{
    return m_isSearching;
}

void BaseFileFind::cancel()
{
    m_watcher.cancel();
}

con's avatar
con committed
83
84
85
86
QStringList BaseFileFind::fileNameFilters() const
{
    QStringList filters;
    if (m_filterCombo && !m_filterCombo->currentText().isEmpty()) {
87
        const QStringList parts = m_filterCombo->currentText().split(QLatin1Char(','));
con's avatar
con committed
88
        foreach (const QString &part, parts) {
89
            const QString filter = part.trimmed();
con's avatar
con committed
90
91
92
93
94
95
96
97
            if (!filter.isEmpty()) {
                filters << filter;
            }
        }
    }
    return filters;
}

98
void BaseFileFind::findAll(const QString &txt, Find::FindFlags findFlags)
con's avatar
con committed
99
100
101
{
    m_isSearching = true;
    emit changed();
con's avatar
con committed
102
    if (m_filterCombo)
103
        updateComboEntries(m_filterCombo, true);
104
    m_watcher.setFuture(QFuture<FileSearchResultList>());
105
106
    SearchResult *result = m_resultWindow->startNewSearch();
    connect(result, SIGNAL(activated(Find::SearchResultItem)), this, SLOT(openEditor(Find::SearchResultItem)));
con's avatar
con committed
107
    m_resultWindow->popup(true);
108
    if (findFlags & Find::FindRegularExpression) {
109
110
111
112
113
114
        m_watcher.setFuture(Utils::findInFilesRegExp(txt, files(),
            textDocumentFlagsForFindFlags(findFlags), ITextEditor::openedTextEditorsContents()));
    } else {
        m_watcher.setFuture(Utils::findInFiles(txt, files(),
            textDocumentFlagsForFindFlags(findFlags), ITextEditor::openedTextEditorsContents()));
    }
115
    Core::FutureProgress *progress =
116
        Core::ICore::instance()->progressManager()->addTask(m_watcher.future(),
con's avatar
con committed
117
                                                                        "Search",
118
                                                                        Constants::TASK_SEARCH);
con's avatar
con committed
119
120
121
122
    progress->setWidget(createProgressWidget());
    connect(progress, SIGNAL(clicked()), m_resultWindow, SLOT(popup()));
}

123
void BaseFileFind::replaceAll(const QString &txt, Find::FindFlags findFlags)
124
125
126
127
{
    m_isSearching = true;
    emit changed();
    if (m_filterCombo)
128
        updateComboEntries(m_filterCombo, true);
129
    m_watcher.setFuture(QFuture<FileSearchResultList>());
130
131
132
133
134
    SearchResult *result = m_resultWindow->startNewSearch(SearchResultWindow::SearchAndReplace);
    connect(result, SIGNAL(activated(Find::SearchResultItem)), this, SLOT(openEditor(Find::SearchResultItem)));
    connect(result, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
            this, SLOT(doReplace(QString,QList<Find::SearchResultItem>)));
    m_resultWindow->popup(true);
135
    if (findFlags & Find::FindRegularExpression) {
136
137
138
139
140
141
        m_watcher.setFuture(Utils::findInFilesRegExp(txt, files(),
            textDocumentFlagsForFindFlags(findFlags), ITextEditor::openedTextEditorsContents()));
    } else {
        m_watcher.setFuture(Utils::findInFiles(txt, files(),
            textDocumentFlagsForFindFlags(findFlags), ITextEditor::openedTextEditorsContents()));
    }
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    Core::FutureProgress *progress =
        Core::ICore::instance()->progressManager()->addTask(m_watcher.future(),
                                                                        "Search",
                                                                        Constants::TASK_SEARCH);
    progress->setWidget(createProgressWidget());
    connect(progress, SIGNAL(clicked()), m_resultWindow, SLOT(popup()));
}

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);
        m_resultWindow->hide();
    }
}

con's avatar
con committed
161
void BaseFileFind::displayResult(int index) {
162
163
164
165
    Utils::FileSearchResultList results = m_watcher.future().resultAt(index);
    QList<Find::SearchResultItem> items; // this conversion is stupid...
    foreach (const Utils::FileSearchResult &result, results) {
        Find::SearchResultItem item;
166
        item.path = QStringList() << QDir::toNativeSeparators(result.fileName);
167
        item.lineNumber = result.lineNumber;
168
169
170
171
        item.text = result.matchingLine;
        item.textMarkLength = result.matchLength;
        item.textMarkPos = result.matchStart;
        item.useTextEditorFont = true;
172
173
174
        item.userData = result.regexpCapturedTexts;
        items << item;
    }
175
    m_resultWindow->addResults(items, Find::SearchResultWindow::AddOrdered);
con's avatar
con committed
176
177
178
179
180
181
    if (m_resultLabel)
        m_resultLabel->setText(tr("%1 found").arg(m_resultWindow->numberOfResults()));
}

void BaseFileFind::searchFinished()
{
182
    m_resultWindow->finishSearch();
con's avatar
con committed
183
184
185
186
187
188
189
190
    m_isSearching = false;
    m_resultLabel = 0;
    emit changed();
}

QWidget *BaseFileFind::createProgressWidget()
{
    m_resultLabel = new QLabel;
191
    m_resultLabel->setAlignment(Qt::AlignCenter);
con's avatar
con committed
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
    // ### 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()));
    m_resultLabel->setText(tr("%1 found").arg(m_resultWindow->numberOfResults()));
    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
234
235
    if (m_filterCombo)
        syncComboWithSettings(m_filterCombo, m_filterSetting);
con's avatar
con committed
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
}

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()));
    }
}

262
void BaseFileFind::openEditor(const Find::SearchResultItem &item)
con's avatar
con committed
263
{
264
    if (item.path.size() > 0) {
265
        TextEditor::BaseTextEditor::openEditorAt(QDir::fromNativeSeparators(item.path.first()), item.lineNumber, item.textMarkPos,
266
                                                 QString(), Core::EditorManager::ModeSwitch);
267
    } else {
268
        Core::EditorManager::instance()->openEditor(item.text, QString(), Core::EditorManager::ModeSwitch);
269
    }
con's avatar
con committed
270
}
271

con's avatar
con committed
272
// #pragma mark Static methods
273
274
275

static void applyChanges(QTextDocument *doc, const QString &text, const QList<Find::SearchResultItem> &items)
{
276
    QList<QPair<QTextCursor, QString> > changes;
277
278
279
280
281

    foreach (const Find::SearchResultItem &item, items) {
        const int blockNumber = item.lineNumber - 1;
        QTextCursor tc(doc->findBlockByNumber(blockNumber));

282
        const int cursorPosition = tc.position() + item.textMarkPos;
283
284

        int cursorIndex = 0;
285
286
        for (; cursorIndex < changes.size(); ++cursorIndex) {
            const QTextCursor &otherTc = changes.at(cursorIndex).first;
287

288
            if (otherTc.position() == cursorPosition)
289
290
291
                break;
        }

292
        if (cursorIndex != changes.size())
293
294
295
            continue; // skip this change.

        tc.setPosition(cursorPosition);
296
        tc.setPosition(tc.position() + item.textMarkLength,
297
                       QTextCursor::KeepAnchor);
298
299
300
301
302
303
        QString substitutionText;
        if (item.userData.canConvert<QStringList>() && !item.userData.toStringList().isEmpty())
            substitutionText = Utils::expandRegExpReplacement(text, item.userData.toStringList());
        else
            substitutionText = text;
        changes.append(QPair<QTextCursor, QString>(tc, substitutionText));
304
305
    }

306
307
308
309
    for (int i = 0; i < changes.size(); ++i) {
        QPair<QTextCursor, QString> &cursor = changes[i];
        cursor.first.insertText(cursor.second);
    }
310
311
312
313
314
315
316
317
318
319
320
}

QStringList BaseFileFind::replaceAll(const QString &text,
                               const QList<Find::SearchResultItem> &items)
{
    if (text.isEmpty() || items.isEmpty())
        return QStringList();

    QHash<QString, QList<Find::SearchResultItem> > changes;

    foreach (const Find::SearchResultItem &item, items)
321
        changes[QDir::fromNativeSeparators(item.path.first())].append(item);
322
323
324
325
326
327
328
329

    Core::EditorManager *editorManager = Core::EditorManager::instance();

    QHashIterator<QString, QList<Find::SearchResultItem> > it(changes);
    while (it.hasNext()) {
        it.next();

        const QString fileName = it.key();
330
        const QList<Find::SearchResultItem> changeItems = it.value();
331
332
333
334
335
336
337
338
339
340
341
342

        const QList<Core::IEditor *> editors = editorManager->editorsForFileName(fileName);
        TextEditor::BaseTextEditor *textEditor = 0;
        foreach (Core::IEditor *editor, editors) {
            textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor->widget());
            if (textEditor != 0)
                break;
        }

        if (textEditor != 0) {
            QTextCursor tc = textEditor->textCursor();
            tc.beginEditBlock();
343
            applyChanges(textEditor->document(), text, changeItems);
344
345
346
347
348
349
350
351
352
353
354
355
356
            tc.endEditBlock();
        } else {
            QFile file(fileName);

            if (file.open(QFile::ReadOnly)) {
                QTextStream stream(&file);
                // ### set the encoding
                const QString plainText = stream.readAll();
                file.close();

                QTextDocument doc;
                doc.setPlainText(plainText);

357
                applyChanges(&doc, text, changeItems);
358
359
360
361
362
363
364
365
366
367
368
369
370

                QFile newFile(fileName);
                if (newFile.open(QFile::WriteOnly)) {
                    QTextStream stream(&newFile);
                    // ### set the encoding
                    stream << doc.toPlainText();
                }
            }
        }
    }

    return changes.keys();
}