diff --git a/plugins/grepview/grepdialog.cpp b/plugins/grepview/grepdialog.cpp index 1a2525dea2..ecee2f27e5 100644 --- a/plugins/grepview/grepdialog.cpp +++ b/plugins/grepview/grepdialog.cpp @@ -1,497 +1,466 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "grepviewplugin.h" -#include "grepjob.h" #include "grepoutputview.h" #include "grepfindthread.h" #include "greputil.h" using namespace KDevelop; namespace { inline QString allOpenFilesString() { return i18n("All Open Files"); } inline QString allOpenProjectsString() { return i18n("All Open Projects"); } inline QStringList template_desc() { return QStringList() << QStringLiteral("verbatim") << QStringLiteral("word") << QStringLiteral("assignment") << QStringLiteral("->MEMBER(") << QStringLiteral("class::MEMBER(") << QStringLiteral("OBJECT->member("); } inline QStringList template_str() { return QStringList() << QStringLiteral("%s") << QStringLiteral("\\b%s\\b") << QStringLiteral("\\b%s\\b\\s*=[^=]") << QStringLiteral("\\->\\s*\\b%s\\b\\s*\\(") << QStringLiteral("([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(") - << QStringLiteral("\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\("); + << QStringLiteral("\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\("); } inline QStringList repl_template() { return QStringList() << QStringLiteral("%s") << QStringLiteral("%s") << QStringLiteral("%s = ") << QStringLiteral("->%s(") << QStringLiteral("\\1::%s(") << QStringLiteral("%s->\\1("); } inline QStringList filepatterns() { return QStringList() << QStringLiteral("*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.idl,*.c,*.m,*.mm,*.M,*.y,*.ypp,*.yxx,*.y++,*.l") << QStringLiteral("*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.c,*.m,*.mm,*.M") << QStringLiteral("*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.idl") << QStringLiteral("*.adb") << QStringLiteral("*.cs") << QStringLiteral("*.f") << QStringLiteral("*.html,*.htm") << QStringLiteral("*.hs") << QStringLiteral("*.java") << QStringLiteral("*.js") << QStringLiteral("*.php,*.php3,*.php4") << QStringLiteral("*.pl") << QStringLiteral("*.pp,*.pas") << QStringLiteral("*.py") << QStringLiteral("*.js,*.css,*.yml,*.rb,*.rhtml,*.html.erb,*.rjs,*.js.rjs,*.rxml,*.xml.builder") << QStringLiteral("CMakeLists.txt,*.cmake") << QStringLiteral("*"); } inline QStringList excludepatterns() { return QStringList() << QStringLiteral("/CVS/,/SCCS/,/.svn/,/_darcs/,/build/,/.git/") << QLatin1String(""); } ///Separator used to separate search paths. inline QString pathsSeparator() { return (QStringLiteral(";")); } ///Max number of items in paths combo box. const int pathsMaxCount = 25; } GrepDialog::GrepDialog( GrepViewPlugin * plugin, QWidget *parent ) : QDialog(parent), Ui::GrepWidget(), m_plugin( plugin ) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle( i18n("Find/Replace in Files") ); setupUi(this); adjustSize(); auto searchButton = buttonBox->button(QDialogButtonBox::Ok); Q_ASSERT(searchButton); searchButton->setText(i18nc("@action:button", "Search...")); searchButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); connect(searchButton, &QPushButton::clicked, this, &GrepDialog::startSearch); connect(buttonBox, &QDialogButtonBox::rejected, this, &GrepDialog::reject); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); patternCombo->addItems( cg.readEntry("LastSearchItems", QStringList()) ); patternCombo->setInsertPolicy(QComboBox::InsertAtTop); templateTypeCombo->addItems(template_desc()); templateTypeCombo->setCurrentIndex( cg.readEntry("LastUsedTemplateIndex", 0) ); templateEdit->addItems( cg.readEntry("LastUsedTemplateString", template_str()) ); templateEdit->setEditable(true); templateEdit->setCompletionMode(KCompletion::CompletionPopup); KCompletion* comp = templateEdit->completionObject(); connect(templateEdit, static_cast(&KComboBox::returnPressed), comp, static_cast(&KCompletion::addItem)); for(int i=0; icount(); i++) comp->addItem(templateEdit->itemText(i)); replacementTemplateEdit->addItems( cg.readEntry("LastUsedReplacementTemplateString", repl_template()) ); replacementTemplateEdit->setEditable(true); replacementTemplateEdit->setCompletionMode(KCompletion::CompletionPopup); comp = replacementTemplateEdit->completionObject(); connect(replacementTemplateEdit, static_cast(&KComboBox::returnPressed), comp, static_cast(&KCompletion::addItem)); for(int i=0; icount(); i++) comp->addItem(replacementTemplateEdit->itemText(i)); regexCheck->setChecked(cg.readEntry("regexp", false )); caseSensitiveCheck->setChecked(cg.readEntry("case_sens", true)); searchPaths->setCompletionObject(new KUrlCompletion()); searchPaths->setAutoDeleteCompletionObject(true); QList projects = m_plugin->core()->projectController()->projects(); searchPaths->addItems(cg.readEntry("SearchPaths", QStringList(!projects.isEmpty() ? allOpenProjectsString() : QDir::homePath() ) )); searchPaths->setInsertPolicy(QComboBox::InsertAtTop); syncButton->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); syncButton->setMenu(createSyncButtonMenu()); depthSpin->setValue(cg.readEntry("depth", -1)); limitToProjectCheck->setChecked(cg.readEntry("search_project_files", true)); filesCombo->addItems(cg.readEntry("file_patterns", filepatterns())); excludeCombo->addItems(cg.readEntry("exclude_patterns", excludepatterns()) ); connect(templateTypeCombo, static_cast(&KComboBox::activated), this, &GrepDialog::templateTypeComboActivated); connect(patternCombo, &QComboBox::editTextChanged, this, &GrepDialog::patternComboEditTextChanged); patternComboEditTextChanged( patternCombo->currentText() ); patternCombo->setFocus(); connect(searchPaths, static_cast(&KComboBox::activated), this, &GrepDialog::setSearchLocations); directorySelector->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); connect(directorySelector, &QPushButton::clicked, this, &GrepDialog::selectDirectoryDialog ); directoryChanged(directorySelector->text()); } void GrepDialog::selectDirectoryDialog() { const QString dirName = QFileDialog::getExistingDirectory( this, i18nc("@title:window", "Select directory to search in"), searchPaths->lineEdit()->text()); if (!dirName.isEmpty()) { setSearchLocations(dirName); } } void GrepDialog::addUrlToMenu(QMenu* menu, const QUrl& url) { QAction* action = menu->addAction(m_plugin->core()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain)); action->setData(QVariant(url.toString(QUrl::PreferLocalFile))); connect(action, &QAction::triggered, this, &GrepDialog::synchronizeDirActionTriggered); } void GrepDialog::addStringToMenu(QMenu* menu, QString string) { QAction* action = menu->addAction(string); action->setData(QVariant(string)); connect(action, &QAction::triggered, this, &GrepDialog::synchronizeDirActionTriggered); } void GrepDialog::synchronizeDirActionTriggered(bool) { QAction* action = qobject_cast(sender()); Q_ASSERT(action); setSearchLocations(action->data().toString()); } QMenu* GrepDialog::createSyncButtonMenu() { QMenu* ret = new QMenu; QSet hadUrls; IDocument *doc = m_plugin->core()->documentController()->activeDocument(); if ( doc ) { Path url = Path(doc->url()).parent(); // always add the current file's parent directory hadUrls.insert(url); addUrlToMenu(ret, url.toUrl()); url = url.parent(); while(m_plugin->core()->projectController()->findProjectForUrl(url.toUrl())) { if(hadUrls.contains(url)) break; hadUrls.insert(url); addUrlToMenu(ret, url.toUrl()); url = url.parent(); } } foreach(IProject* project, m_plugin->core()->projectController()->projects()) { if (!hadUrls.contains(project->path())) { addUrlToMenu(ret, project->path().toUrl()); } } addStringToMenu(ret, allOpenFilesString()); addStringToMenu(ret, allOpenProjectsString()); return ret; } void GrepDialog::directoryChanged(const QString& dir) { QUrl currentUrl = QUrl::fromLocalFile(dir); if( !currentUrl.isValid() ) { - setEnableProjectBox(false); + m_settings.projectFilesOnly = false; return; } bool projectAvailable = true; foreach(const QUrl& url, getDirectoryChoice()) { IProject *proj = ICore::self()->projectController()->findProjectForUrl( url ); if( !proj || !proj->path().toUrl().isLocalFile() ) projectAvailable = false; } - setEnableProjectBox(projectAvailable); + m_settings.projectFilesOnly = projectAvailable; } GrepDialog::~GrepDialog() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); // memorize the last patterns and paths cg.writeEntry("LastSearchItems", qCombo2StringList(patternCombo)); cg.writeEntry("regexp", regexCheck->isChecked()); cg.writeEntry("depth", depthSpin->value()); cg.writeEntry("search_project_files", limitToProjectCheck->isChecked()); cg.writeEntry("case_sens", caseSensitiveCheck->isChecked()); cg.writeEntry("exclude_patterns", qCombo2StringList(excludeCombo)); cg.writeEntry("file_patterns", qCombo2StringList(filesCombo)); cg.writeEntry("LastUsedTemplateIndex", templateTypeCombo->currentIndex()); cg.writeEntry("LastUsedTemplateString", qCombo2StringList(templateEdit)); cg.writeEntry("LastUsedReplacementTemplateString", qCombo2StringList(replacementTemplateEdit)); cg.writeEntry("SearchPaths", qCombo2StringList(searchPaths)); cg.sync(); } void GrepDialog::templateTypeComboActivated(int index) { templateEdit->setCurrentItem( template_str().at(index), true ); replacementTemplateEdit->setCurrentItem( repl_template().at(index), true ); } -void GrepDialog::setEnableProjectBox(bool enable) +void GrepDialog::setSettings(const GrepJobSettings& settings) { - limitToProjectCheck->setEnabled(enable); - limitToProjectLabel->setEnabled(enable); + patternCombo->setEditText(settings.pattern); + patternComboEditTextChanged(settings.pattern); + m_settings.pattern = settings.pattern; + + limitToProjectCheck->setEnabled(settings.projectFilesOnly); + limitToProjectLabel->setEnabled(settings.projectFilesOnly); + m_settings.projectFilesOnly = settings.projectFilesOnly; + + // Note: everything else is set by a user } -void GrepDialog::setPattern(const QString &pattern) +GrepJobSettings GrepDialog::settings() const { - patternCombo->setEditText(pattern); - patternComboEditTextChanged(pattern); + return m_settings; } void GrepDialog::setSearchLocations(const QString &dir) { if(!dir.isEmpty()) { if(QDir::isAbsolutePath(dir)) { static_cast(searchPaths->completionObject())->setDir( QUrl::fromLocalFile(dir) ); } if (searchPaths->contains(dir)) { searchPaths->removeItem(searchPaths->findText(dir)); } searchPaths->insertItem(0, dir); searchPaths->setCurrentItem(dir); if (searchPaths->count() > pathsMaxCount) { searchPaths->removeItem(searchPaths->count() - 1); } } directoryChanged(dir); } -QString GrepDialog::patternString() const -{ - return patternCombo->currentText(); -} - -QString GrepDialog::templateString() const -{ - return templateEdit->currentText().isEmpty() ? QStringLiteral("%s") : templateEdit->currentText(); -} - -QString GrepDialog::replacementTemplateString() const -{ - return replacementTemplateEdit->currentText(); -} - -QString GrepDialog::filesString() const -{ - return filesCombo->currentText(); -} - -QString GrepDialog::excludeString() const -{ - return excludeCombo->currentText(); -} - -bool GrepDialog::useProjectFilesFlag() const -{ - if (!limitToProjectCheck->isEnabled()) return false; - return limitToProjectCheck->isChecked(); -} - -bool GrepDialog::regexpFlag() const -{ - return regexCheck->isChecked(); -} - -int GrepDialog::depthValue() const -{ - return depthSpin->value(); -} - -bool GrepDialog::caseSensitiveFlag() const -{ - return caseSensitiveCheck->isChecked(); -} - void GrepDialog::patternComboEditTextChanged( const QString& text) { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); } QList< QUrl > GrepDialog::getDirectoryChoice() const { QList< QUrl > ret; QString text = searchPaths->currentText(); if(text == allOpenFilesString()) { foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) ret << doc->url(); }else if(text == allOpenProjectsString()) { foreach(IProject* project, ICore::self()->projectController()->projects()) ret << project->path().toUrl(); }else{ QStringList semicolonSeparatedFileList = text.split(pathsSeparator()); if(!semicolonSeparatedFileList.isEmpty() && QFileInfo::exists(semicolonSeparatedFileList[0])) { // We use QFileInfo to make sure this is really a semicolon-separated file list, not a file containing // a semicolon in the name. foreach(const QString& file, semicolonSeparatedFileList) ret << QUrl::fromLocalFile(file); }else{ ret << QUrl::fromUserInput(searchPaths->currentText()); } } return ret; } bool GrepDialog::isPartOfChoice(QUrl url) const { foreach(const QUrl& choice, getDirectoryChoice()) if(choice.isParentOf(url) || choice == url) return true; return false; } void GrepDialog::startSearch() { + updateSettings(); + // search for unsaved documents QList unsavedFiles; - QStringList include = GrepFindFilesThread::parseInclude(filesString()); - QStringList exclude = GrepFindFilesThread::parseExclude(excludeString()); + QStringList include = GrepFindFilesThread::parseInclude(m_settings.files); + QStringList exclude = GrepFindFilesThread::parseExclude(m_settings.exclude); foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) { QUrl docUrl = doc->url(); if(doc->state() != IDocument::Clean && isPartOfChoice(docUrl) && QDir::match(include, docUrl.fileName()) && !QDir::match(exclude, docUrl.toLocalFile())) { unsavedFiles << doc; } } if(!ICore::self()->documentController()->saveSomeDocuments(unsavedFiles)) { close(); return; } QList choice = getDirectoryChoice(); GrepJob* job = m_plugin->newGrepJob(); const QString descriptionOrUrl(searchPaths->currentText()); QString description = descriptionOrUrl; // Shorten the description if(descriptionOrUrl != allOpenFilesString() && descriptionOrUrl != allOpenProjectsString()) { auto prettyFileName = [](const QUrl& url) { return ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain); }; if (choice.size() > 1) { description = i18np("%2, and %1 more item", "%2, and %1 more items", choice.size() - 1, prettyFileName(choice[0])); } else if (!choice.isEmpty()) { description = prettyFileName(choice[0]); } } GrepOutputView *toolView = (GrepOutputView*)ICore::self()->uiController()-> findToolView(i18n("Find/Replace in Files"), m_plugin->toolViewFactory(), IUiController::CreateAndRaise); - GrepOutputModel* outputModel = toolView->renewModel(patternString(), description); + GrepOutputModel* outputModel = toolView->renewModel(m_settings.pattern, description); connect(job, &GrepJob::showErrorMessage, toolView, &GrepOutputView::showErrorMessage); //the GrepOutputModel gets the 'showMessage' signal to store it and forward //it to toolView connect(job, &GrepJob::showMessage, outputModel, &GrepOutputModel::showMessageSlot); connect(outputModel, &GrepOutputModel::showMessage, toolView, &GrepOutputView::showMessage); connect(toolView, &GrepOutputView::outputViewIsClosed, job, [=]() {job->kill();}); job->setOutputModel(outputModel); - job->setPatternString(patternString()); - job->setReplacementTemplateString(replacementTemplateString()); - job->setTemplateString(templateString()); - job->setFilesString(filesString()); - job->setExcludeString(excludeString()); job->setDirectoryChoice(choice); - job->setProjectFilesFlag( useProjectFilesFlag() ); - job->setRegexpFlag( regexpFlag() ); - job->setDepth( depthValue() ); - job->setCaseSensitive( caseSensitiveFlag() ); + job->setSettings(m_settings); ICore::self()->runController()->registerJob(job); m_plugin->rememberSearchDirectory(descriptionOrUrl); close(); } +void GrepDialog::updateSettings() +{ + if (limitToProjectCheck->isEnabled()) + m_settings.projectFilesOnly = limitToProjectCheck->isChecked(); + + m_settings.caseSensitive = caseSensitiveCheck->isChecked(); + m_settings.regexp = regexCheck->isChecked(); + + m_settings.depth = depthSpin->value(); + + m_settings.pattern = patternCombo->currentText(); + m_settings.searchTemplate = templateEdit->currentText().isEmpty() ? QStringLiteral("%s") : templateEdit->currentText(); + m_settings.replacementTemplate = replacementTemplateEdit->currentText(); + m_settings.files = filesCombo->currentText(); + m_settings.exclude = excludeCombo->currentText(); +} diff --git a/plugins/grepview/grepdialog.h b/plugins/grepview/grepdialog.h index 10542e093c..dceb4b3cff 100644 --- a/plugins/grepview/grepdialog.h +++ b/plugins/grepview/grepdialog.h @@ -1,80 +1,72 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPDIALOG_H #define KDEVPLATFORM_PLUGIN_GREPDIALOG_H #include #include +#include "grepjob.h" #include "ui_grepwidget.h" class KConfig; class KUrlRequester; class GrepViewPlugin; class QLineEdit; class GrepDialog : public QDialog, private Ui::GrepWidget { Q_OBJECT public: explicit GrepDialog( GrepViewPlugin * plugin, QWidget *parent=nullptr ); ~GrepDialog() override; - void setPattern(const QString &pattern); - void setEnableProjectBox(bool enable); - - QString patternString() const; - QString templateString() const; - QString replacementTemplateString() const; - QString filesString() const; - QString excludeString() const; - - bool useProjectFilesFlag() const; - bool regexpFlag() const; - bool caseSensitiveFlag() const; - - int depthValue() const; + void setSettings(const GrepJobSettings &settings); + GrepJobSettings settings() const; public Q_SLOTS: void startSearch(); ///Sets directory(ies)/files to search in. Also it can be semicolon separated list of directories/files or one of special strings: allOpenFilesString, allOpenProjectsString void setSearchLocations(const QString &dir); private Q_SLOTS: void templateTypeComboActivated(int); void patternComboEditTextChanged( const QString& ); void directoryChanged(const QString &dir); QMenu* createSyncButtonMenu(); void addUrlToMenu(QMenu* ret, const QUrl& url); void addStringToMenu(QMenu* ret, QString string); void synchronizeDirActionTriggered(bool); ///Opens the dialog to select a directory to search in, and inserts it into Location(s) field. void selectDirectoryDialog(); private: // Returns the chosen directories or files (only the top directories, not subfiles) QList< QUrl > getDirectoryChoice() const; // Returns whether the given url is a subfile/subdirectory of one of the chosen directories/files // This is slow, so don't call it too often bool isPartOfChoice(QUrl url) const; + // Checks what a user has entered into the dialog and saves the data in m_settings + void updateSettings(); GrepViewPlugin * m_plugin; + GrepJobSettings m_settings; }; #endif diff --git a/plugins/grepview/grepjob.cpp b/plugins/grepview/grepjob.cpp index 57b0628ee2..b7516f42f2 100644 --- a/plugins/grepview/grepjob.cpp +++ b/plugins/grepview/grepjob.cpp @@ -1,333 +1,293 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2008 by Hamish Rodda * * rodda@kde.org * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepjob.h" #include "grepoutputmodel.h" #include "greputil.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputItem::List grepFile(const QString &filename, const QRegExp &re) { GrepOutputItem::List res; QFile file(filename); if(!file.open(QIODevice::ReadOnly)) return res; int lineno = 0; // detect encoding (unicode files can be feed forever, stops when confidence reachs 99% KEncodingProber prober; while(!file.atEnd() && prober.state() == KEncodingProber::Probing && prober.confidence() < 0.99) { prober.feed(file.read(0xFF)); } // reads file with detected encoding file.seek(0); QTextStream stream(&file); if(prober.confidence()>0.7) stream.setCodec(prober.encoding()); while( !stream.atEnd() ) { QString data = stream.readLine(); // remove line terminators (in order to not match them) for(int pos = data.length()-1; pos >= 0 && (data[pos] == '\r' || data[pos] == '\n'); pos--) { data.chop(1); } int offset = 0; // allow empty string matching result in an infinite loop ! while( re.indexIn(data, offset)!=-1 && re.cap(0).length() > 0 ) { int start = re.pos(0); int end = start + re.cap(0).length(); DocumentChangePointer change = DocumentChangePointer(new DocumentChange( IndexedString(filename), KTextEditor::Range(lineno, start, lineno, end), re.cap(0), QString())); res << GrepOutputItem(change, data, false); offset = end; } lineno++; } file.close(); return res; } GrepJob::GrepJob( QObject* parent ) : KJob( parent ) , m_workState(WorkIdle) , m_fileIndex(0) - , m_useProjectFilesFlag(false) - , m_regexpFlag(true) - , m_caseSensitiveFlag(true) - , m_depthValue(-1) , m_findSomething(false) { setCapabilities(Killable); KDevelop::ICore::self()->uiController()->registerStatus(this); connect(this, &GrepJob::result, this, &GrepJob::testFinishState); } QString GrepJob::statusName() const { return i18n("Find in Files"); } void GrepJob::slotFindFinished() { if(m_findThread && !m_findThread->triesToAbort()) { m_fileList = m_findThread->files(); delete m_findThread; } else { m_fileList.clear(); emit hideProgress(this); emit clearMessage(this); m_errorMessage = i18n("Search aborted"); emitResult(); return; } if(m_fileList.isEmpty()) { m_workState = WorkIdle; emit hideProgress(this); emit clearMessage(this); m_errorMessage = i18n("No files found matching the wildcard patterns"); //model()->slotFailed(); emitResult(); return; } - if(!m_regexpFlag) + if(!m_settings.regexp) { - m_patternString = QRegExp::escape(m_patternString); + m_settings.pattern = QRegExp::escape(m_settings.pattern); } - if(m_regexpFlag && QRegExp(m_patternString).captureCount() > 0) + if(m_settings.regexp && QRegExp(m_settings.pattern).captureCount() > 0) { m_workState = WorkIdle; emit hideProgress(this); emit clearMessage(this); m_errorMessage = i18nc("Capture is the text which is \"captured\" with () in regular expressions " "see http://doc.trolltech.com/qregexp.html#capturedTexts", "Captures are not allowed in pattern string"); emitResult(); return; } - QString pattern = substitudePattern(m_templateString, m_patternString); + QString pattern = substitudePattern(m_settings.searchTemplate, m_settings.pattern); m_regExp.setPattern(pattern); m_regExp.setPatternSyntax(QRegExp::RegExp2); - m_regExp.setCaseSensitivity( m_caseSensitiveFlag ? Qt::CaseSensitive : Qt::CaseInsensitive ); + m_regExp.setCaseSensitivity( m_settings.caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive ); if(pattern == QRegExp::escape(pattern)) { // enable wildcard mode when possible // if pattern has already been escaped (raw text serch) a second escape will result in a different string anyway m_regExp.setPatternSyntax(QRegExp::Wildcard); } m_outputModel->setRegExp(m_regExp); - m_outputModel->setReplacementTemplate(m_replacementTemplateString); + m_outputModel->setReplacementTemplate(m_settings.replacementTemplate); emit showMessage(this, i18np("Searching for %2 in one file", "Searching for %2 in %1 files", m_fileList.length(), m_regExp.pattern().toHtmlEscaped())); m_workState = WorkGrep; QMetaObject::invokeMethod( this, "slotWork", Qt::QueuedConnection); } void GrepJob::slotWork() { switch(m_workState) { case WorkIdle: m_workState = WorkCollectFiles; m_fileIndex = 0; emit showProgress(this, 0,0,0); QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); break; case WorkCollectFiles: - m_findThread = new GrepFindFilesThread(this, m_directoryChoice, m_depthValue, m_filesString, m_excludeString, m_useProjectFilesFlag); + m_findThread = new GrepFindFilesThread(this, m_directoryChoice, m_settings.depth, m_settings.files, m_settings.exclude, m_settings.projectFilesOnly); emit showMessage(this, i18n("Collecting files...")); connect(m_findThread.data(), &GrepFindFilesThread::finished, this, &GrepJob::slotFindFinished); m_findThread->start(); break; case WorkGrep: if(m_fileIndex < m_fileList.length()) { emit showProgress(this, 0, m_fileList.length(), m_fileIndex); if(m_fileIndex < m_fileList.length()) { QString file = m_fileList[m_fileIndex].toLocalFile(); GrepOutputItem::List items = grepFile(file, m_regExp); if(!items.isEmpty()) { m_findSomething = true; emit foundMatches(file, items); } m_fileIndex++; } QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); } else { emit hideProgress(this); emit clearMessage(this); m_workState = WorkIdle; //model()->slotCompleted(); emitResult(); } break; case WorkCancelled: emit hideProgress(this); emit clearMessage(this); emit showErrorMessage(i18n("Search aborted"), 5000); emitResult(); break; } } void GrepJob::start() { if(m_workState!=WorkIdle) return; m_fileList.clear(); m_workState = WorkIdle; m_fileIndex = 0; m_findSomething = false; m_outputModel->clear(); qRegisterMetaType(); connect(this, &GrepJob::foundMatches, m_outputModel, &GrepOutputModel::appendOutputs, Qt::QueuedConnection); QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); } bool GrepJob::doKill() { if(m_workState!=WorkIdle && !m_findThread.isNull()) { m_workState = WorkIdle; m_findThread->tryAbort(); return false; } else { m_workState = WorkCancelled; } return true; } void GrepJob::testFinishState(KJob *job) { if(!job->error()) { if (!m_errorMessage.isEmpty()) { emit showErrorMessage(i18n("Failed: %1", m_errorMessage)); } else if (!m_findSomething) { emit showMessage(this, i18n("No results found")); } } } void GrepJob::setOutputModel(GrepOutputModel* model) { m_outputModel = model; } -void GrepJob::setTemplateString(const QString& templateString) -{ - m_templateString = templateString; -} - -void GrepJob::setReplacementTemplateString(const QString &replTmplString) -{ - m_replacementTemplateString = replTmplString; -} - -void GrepJob::setFilesString(const QString& filesString) -{ - m_filesString = filesString; -} - -void GrepJob::setExcludeString(const QString& excludeString) -{ - m_excludeString = excludeString; -} - void GrepJob::setDirectoryChoice(const QList& choice) { m_directoryChoice = choice; } -void GrepJob::setCaseSensitive(bool caseSensitive) -{ - m_caseSensitiveFlag = caseSensitive; -} - -void GrepJob::setDepth(int depth) +void GrepJob::setSettings(const GrepJobSettings& settings) { - m_depthValue = depth; -} + m_settings = settings; -void GrepJob::setRegexpFlag(bool regexpFlag) -{ - m_regexpFlag = regexpFlag; + setObjectName(i18n("Grep: %1", m_settings.pattern)); } -void GrepJob::setProjectFilesFlag(bool projectFilesFlag) +GrepJobSettings GrepJob::settings() const { - m_useProjectFilesFlag = projectFilesFlag; + return m_settings; } - -void GrepJob::setPatternString(const QString& patternString) -{ - m_patternString = patternString; - - setObjectName(i18n("Grep: %1", m_patternString)); -} - diff --git a/plugins/grepview/grepjob.h b/plugins/grepview/grepjob.h index 3ef99efbdc..4591535c59 100644 --- a/plugins/grepview/grepjob.h +++ b/plugins/grepview/grepjob.h @@ -1,123 +1,123 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2008 by Hamish Rodda * * rodda@kde.org * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPJOB_H #define KDEVPLATFORM_PLUGIN_GREPJOB_H #include #include #include #include #include "grepfindthread.h" #include "grepoutputmodel.h" namespace KDevelop { class IProject; class ProcessLineMaker; } class QRegExp; class GrepViewPlugin; class FindReplaceTest; //FIXME: this is useful only for tests +struct GrepJobSettings +{ + bool projectFilesOnly = false; + bool caseSensitive = true; + bool regexp = true; + + int depth = -1; + + QString pattern; + QString searchTemplate; + QString replacementTemplate; + QString files; + QString exclude; +}; + class GrepJob : public KJob, public KDevelop::IStatus { Q_OBJECT Q_INTERFACES( KDevelop::IStatus ) friend class GrepViewPlugin; friend class FindReplaceTest; private: ///Job can only be instanciated by plugin explicit GrepJob( QObject *parent = nullptr ); public: + void setSettings(const GrepJobSettings& settings); + GrepJobSettings settings() const; void setOutputModel(GrepOutputModel * model); - void setPatternString(const QString& patternString); - void setTemplateString(const QString &templateString); - void setReplacementTemplateString(const QString &replTmplString); - void setFilesString(const QString &filesString); - void setExcludeString(const QString &excludeString); void setDirectoryChoice(const QList &choice); - void setDepth(int depth); - void setRegexpFlag(bool regexpFlag); - void setCaseSensitive(bool caseSensitive); - void setProjectFilesFlag(bool projectFilesFlag); void start() override; QString statusName() const override; protected: bool doKill() override; // GrepOutputModel* model() const; private Q_SLOTS: void slotFindFinished(); void testFinishState(KJob *job); Q_SIGNALS: void clearMessage( KDevelop::IStatus* ) override; void showMessage( KDevelop::IStatus*, const QString & message, int timeout = 0) override; void showErrorMessage(const QString & message, int timeout = 0) override; void hideProgress( KDevelop::IStatus* ) override; void showProgress( KDevelop::IStatus*, int minimum, int maximum, int value) override; void foundMatches( const QString& filename, const GrepOutputItem::List& matches); private: Q_INVOKABLE void slotWork(); - QString m_patternString; + QList m_directoryChoice; + QString m_errorMessage; + QRegExp m_regExp; QString m_regExpSimple; GrepOutputModel *m_outputModel; enum { WorkCollectFiles, WorkGrep, WorkIdle, WorkCancelled } m_workState; QList m_fileList; int m_fileIndex; QPointer m_findThread; - QString m_errorMessage; - QString m_templateString; - QString m_replacementTemplateString; - QString m_filesString; - QString m_excludeString; - QList m_directoryChoice; - - bool m_useProjectFilesFlag; - bool m_regexpFlag; - bool m_caseSensitiveFlag; - int m_depthValue; + GrepJobSettings m_settings; bool m_findSomething; }; //FIXME: this function is used externally only for tests, find a way to keep it // static for a regular compilation GrepOutputItem::List grepFile(const QString &filename, const QRegExp &re); #endif diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index 974cd61d2f..f954819553 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,385 +1,394 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepoutputview.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "ui_grepoutputview.h" #include "grepviewplugin.h" #include "grepdialog.h" #include "greputil.h" #include "grepjob.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputViewFactory::GrepOutputViewFactory(GrepViewPlugin* plugin) : m_plugin(plugin) {} QWidget* GrepOutputViewFactory::create(QWidget* parent) { return new GrepOutputView(parent, m_plugin); } Qt::DockWidgetArea GrepOutputViewFactory::defaultPosition() { return Qt::BottomDockWidgetArea; } QString GrepOutputViewFactory::id() const { return QStringLiteral("org.kdevelop.GrepOutputView"); } const int GrepOutputView::HISTORY_SIZE = 5; GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) , m_next(nullptr) , m_prev(nullptr) , m_collapseAll(nullptr) , m_expandAll(nullptr) , m_clearSearchHistory(nullptr) , m_statusLabel(nullptr) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-find"), windowIcon())); m_prev = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("&Previous Item"), this); m_prev->setEnabled(false); m_next = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("&Next Item"), this); m_next->setEnabled(false); m_collapseAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-left-double")), i18n("C&ollapse All"), this); // TODO change icon m_collapseAll->setEnabled(false); m_expandAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-right-double")), i18n("&Expand All"), this); // TODO change icon m_expandAll->setEnabled(false); QAction *separator = new QAction(this); separator->setSeparator(true); QAction *newSearchAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("New &Search"), this); m_clearSearchHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear Search History"), this); + QAction *refreshAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Refresh"), this); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); + addAction(refreshAction); addAction(m_clearSearchHistory); separator = new QAction(this); separator->setSeparator(true); addAction(separator); QWidgetAction *statusWidget = new QWidgetAction(this); m_statusLabel = new QLabel(this); statusWidget->setDefaultWidget(m_statusLabel); addAction(statusWidget); modelSelector->setEditable(false); modelSelector->setContextMenuPolicy(Qt::CustomContextMenu); connect(modelSelector, &KComboBox::customContextMenuRequested, this, &GrepOutputView::modelSelectorContextMenu); connect(modelSelector, static_cast(&KComboBox::currentIndexChanged), this, &GrepOutputView::changeModel); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, &QAction::triggered, this, &GrepOutputView::selectPreviousItem); connect(m_next, &QAction::triggered, this, &GrepOutputView::selectNextItem); connect(m_collapseAll, &QAction::triggered, this, &GrepOutputView::collapseAllItems); connect(m_expandAll, &QAction::triggered, this, &GrepOutputView::expandAllItems); connect(applyButton, &QPushButton::clicked, this, &GrepOutputView::onApply); connect(m_clearSearchHistory, &QAction::triggered, this, &GrepOutputView::clearSearchHistory); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); applyButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(replacementCombo, &KComboBox::editTextChanged, this, &GrepOutputView::replacementTextChanged); connect(replacementCombo, static_cast(&KComboBox::returnPressed), this, &GrepOutputView::onApply); connect(newSearchAction, &QAction::triggered, this, &GrepOutputView::showDialog); + connect(refreshAction, &QAction::triggered, this, &GrepOutputView::refresh); + resultsTreeView->header()->setStretchLastSection(true); resultsTreeView->header()->setStretchLastSection(true); updateCheckable(); } void GrepOutputView::replacementTextChanged(QString) { updateCheckable(); if (model()) { // see https://bugs.kde.org/show_bug.cgi?id=274902 - renewModel can trigger a call here without an active model updateApplyState(model()->index(0, 0), model()->index(0, 0)); } } GrepOutputView::~GrepOutputView() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); cg.writeEntry("LastReplacementItems", qCombo2StringList(replacementCombo, true)); emit outputViewIsClosed(); } GrepOutputModel* GrepOutputView::renewModel(const QString& name, const QString& description) { // Crear oldest model while(modelSelector->count() > GrepOutputView::HISTORY_SIZE) { QVariant var = modelSelector->itemData(GrepOutputView::HISTORY_SIZE - 1); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(GrepOutputView::HISTORY_SIZE - 1); } replacementCombo->clearEditText(); GrepOutputModel* newModel = new GrepOutputModel(resultsTreeView); applyButton->setEnabled(false); // text may be already present newModel->setReplacement(replacementCombo->currentText()); connect(newModel, &GrepOutputModel::rowsRemoved, this, &GrepOutputView::rowsRemoved); connect(resultsTreeView, &QTreeView::activated, newModel, &GrepOutputModel::activate); connect(replacementCombo, &KComboBox::editTextChanged, newModel, &GrepOutputModel::setReplacement); connect(newModel, &GrepOutputModel::rowsInserted, this, &GrepOutputView::expandElements); connect(newModel, &GrepOutputModel::showErrorMessage, this, &GrepOutputView::showErrorMessage); connect(m_plugin, &GrepViewPlugin::grepJobFinished, this, &GrepOutputView::updateScrollArea); // appends new model to history const QString displayName = i18n("Search \"%1\" in %2 (at time %3)", name, description, QTime::currentTime().toString(QStringLiteral("hh:mm"))); modelSelector->insertItem(0, displayName, qVariantFromValue(newModel)); modelSelector->setCurrentIndex(0);//setCurrentItem(displayName); updateCheckable(); return newModel; } GrepOutputModel* GrepOutputView::model() { return static_cast(resultsTreeView->model()); } void GrepOutputView::changeModel(int index) { if (model()) { disconnect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); disconnect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); } replacementCombo->clearEditText(); //after deleting the whole search history, index is -1 if(index >= 0) { QVariant var = modelSelector->itemData(index); GrepOutputModel *resultModel = static_cast(qvariant_cast(var)); resultsTreeView->setModel(resultModel); connect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); connect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); model()->showMessageEmit(); applyButton->setEnabled(model()->hasResults() && model()->getRootItem() && model()->getRootItem()->checkState() != Qt::Unchecked && !replacementCombo->currentText().isEmpty()); if(model()->hasResults()) expandElements(QModelIndex()); } updateCheckable(); updateApplyState(model()->index(0, 0), model()->index(0, 0)); } void GrepOutputView::setMessage(const QString& msg, MessageType type) { if (type == Error) { QPalette palette = m_statusLabel->palette(); KColorScheme::adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText); m_statusLabel->setPalette(palette); } else { m_statusLabel->setPalette(QPalette()); } m_statusLabel->setText(msg); } void GrepOutputView::showErrorMessage( const QString& errorMessage ) { setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { setMessage(message, Information); } void GrepOutputView::onApply() { if(model()) { Q_ASSERT(model()->rowCount()); // ask a confirmation before an empty string replacement if(replacementCombo->currentText().length() == 0 && KMessageBox::questionYesNo(this, i18n("Do you want to replace with an empty string?"), i18n("Start replacement")) == KMessageBox::No) { return; } setEnabled(false); model()->doReplacements(); setEnabled(true); } } void GrepOutputView::showDialog() { m_plugin->showDialog(true); } +void GrepOutputView::refresh() +{ + m_plugin->showDialog(true, QString(), false); +} + void GrepOutputView::expandElements(const QModelIndex& index) { m_prev->setEnabled(true); m_next->setEnabled(true); m_collapseAll->setEnabled(true); m_expandAll->setEnabled(true); resultsTreeView->expand(index); } void GrepOutputView::selectPreviousItem() { if (!model()) { return; } QModelIndex prev_idx = model()->previousItemIndex(resultsTreeView->currentIndex()); if (prev_idx.isValid()) { resultsTreeView->setCurrentIndex(prev_idx); model()->activate(prev_idx); } } void GrepOutputView::selectNextItem() { if (!model()) { return; } QModelIndex next_idx = model()->nextItemIndex(resultsTreeView->currentIndex()); if (next_idx.isValid()) { resultsTreeView->setCurrentIndex(next_idx); model()->activate(next_idx); } } void GrepOutputView::collapseAllItems() { // Collapse everything resultsTreeView->collapseAll(); // Now reopen the first children, which correspond to the files. resultsTreeView->expand(resultsTreeView->model()->index(0, 0)); } void GrepOutputView::expandAllItems() { resultsTreeView->expandAll(); } void GrepOutputView::rowsRemoved() { m_prev->setEnabled(model()->rowCount()); m_next->setEnabled(model()->rowCount()); } void GrepOutputView::updateApplyState(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(bottomRight); if (!model() || !model()->hasResults()) { applyButton->setEnabled(false); return; } // we only care about the root item if(!topLeft.parent().isValid()) { applyButton->setEnabled(topLeft.data(Qt::CheckStateRole) != Qt::Unchecked && model()->itemsCheckable()); } } void GrepOutputView::updateCheckable() { if(model()) model()->makeItemsCheckable(!replacementCombo->currentText().isEmpty() || model()->itemsCheckable()); } void GrepOutputView::clearSearchHistory() { GrepJob *runningJob = m_plugin->grepJob(); if(runningJob) { runningJob->kill(); } while(modelSelector->count() > 0) { QVariant var = modelSelector->itemData(0); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(0); } applyButton->setEnabled(false); m_statusLabel->setText(QString()); } void GrepOutputView::modelSelectorContextMenu(const QPoint& pos) { QPoint globalPos = modelSelector->mapToGlobal(pos); QMenu myMenu; myMenu.addAction(m_clearSearchHistory); myMenu.exec(globalPos); } void GrepOutputView::updateScrollArea() { for (int col = 0; col < model()->columnCount(); ++col) resultsTreeView->resizeColumnToContents(col); } diff --git a/plugins/grepview/grepoutputview.h b/plugins/grepview/grepoutputview.h index e06b0ba952..0c94502148 100644 --- a/plugins/grepview/grepoutputview.h +++ b/plugins/grepview/grepoutputview.h @@ -1,101 +1,102 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H #define KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H #include #include #include "ui_grepoutputview.h" namespace KDevelop { class IStatus; } class QModelIndex; class GrepViewPlugin; class GrepOutputModel; class GrepOutputDelegate; class GrepOutputViewFactory: public KDevelop::IToolViewFactory { public: explicit GrepOutputViewFactory(GrepViewPlugin* plugin); QWidget* create(QWidget* parent = nullptr) override; Qt::DockWidgetArea defaultPosition() override; QString id() const override; private: GrepViewPlugin* m_plugin; }; class GrepOutputView : public QWidget, Ui::GrepOutputView, public KDevelop::IToolViewActionListener { Q_OBJECT Q_INTERFACES(KDevelop::IToolViewActionListener) public: enum MessageType { Information, Error }; GrepOutputView(QWidget* parent, GrepViewPlugin* plugin); ~GrepOutputView() override; GrepOutputModel* model(); /** * This causes the creation of a new model, the old one is kept in model history. * Oldest models are deleted if needed. * @return pointer to the new model */ GrepOutputModel* renewModel(const QString& name, const QString& description); void setMessage(const QString& msg, MessageType type = Information); public Q_SLOTS: void showErrorMessage( const QString& errorMessage ); void showMessage( KDevelop::IStatus*, const QString& message ); void updateApplyState(const QModelIndex &topLeft, const QModelIndex &bottomRight); void changeModel(int index); void replacementTextChanged(QString); Q_SIGNALS: void outputViewIsClosed(); private: static const int HISTORY_SIZE; QAction* m_next; QAction* m_prev; QAction* m_collapseAll; QAction* m_expandAll; QAction* m_clearSearchHistory; QLabel* m_statusLabel; GrepViewPlugin *m_plugin; private slots: void selectPreviousItem() override; void selectNextItem() override; void collapseAllItems(); void expandAllItems(); void onApply(); void showDialog(); + void refresh(); void expandElements( const QModelIndex & parent ); void rowsRemoved(); void clearSearchHistory(); void modelSelectorContextMenu(const QPoint& pos); void updateScrollArea(); void updateCheckable(); }; #endif // KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H diff --git a/plugins/grepview/grepviewplugin.cpp b/plugins/grepview/grepviewplugin.cpp index 12d5aeaa1f..9f676efe51 100644 --- a/plugins/grepview/grepviewplugin.cpp +++ b/plugins/grepview/grepviewplugin.cpp @@ -1,232 +1,235 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Benjamin Port * * Copyright 2010 Julien Desgats * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepviewplugin.h" #include "grepdialog.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "grepjob.h" #include "grepoutputview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_GREPVIEW, "kdevplatform.plugins.grepview") static QString patternFromSelection(const KDevelop::IDocument* doc) { if (!doc) return QString(); QString pattern; KTextEditor::Range range = doc->textSelection(); if( range.isValid() ) { pattern = doc->textDocument()->text( range ); } if( pattern.isEmpty() ) { pattern = doc->textWord(); } // Before anything, this removes line feeds from the // beginning and the end. int len = pattern.length(); if (len > 0 && pattern[0] == '\n') { pattern.remove(0, 1); len--; } if (len > 0 && pattern[len-1] == '\n') pattern.truncate(len-1); return pattern; } GrepViewPlugin::GrepViewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevgrepview"), parent ), m_currentJob(nullptr) { setXMLFile(QStringLiteral("kdevgrepview.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/GrepViewPlugin"), this, QDBusConnection::ExportScriptableSlots ); QAction*action = actionCollection()->addAction(QStringLiteral("edit_grep")); action->setText(i18n("Find/Replace in Fi&les...")); actionCollection()->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Alt+F")) ); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); action->setToolTip( i18n("Search for expressions over several files") ); action->setWhatsThis( i18n("Opens the 'Find/Replace in files' dialog. There you " "can enter a regular expression which is then " "searched for within all files in the directories " "you specify. Matches will be displayed, you " "can switch to a match directly. You can also do replacement.") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); // instantiate delegate, it's supposed to be deleted via QObject inheritance new GrepOutputDelegate(this); m_factory = new GrepOutputViewFactory(this); core()->uiController()->addToolView(i18n("Find/Replace in Files"), m_factory); } GrepOutputViewFactory* GrepViewPlugin::toolViewFactory() const { return m_factory; } GrepViewPlugin::~GrepViewPlugin() { } void GrepViewPlugin::unload() { core()->uiController()->removeToolView(m_factory); } void GrepViewPlugin::startSearch(QString pattern, QString directory, bool show) { m_directory = directory; showDialog(false, pattern, show); } KDevelop::ContextMenuExtension GrepViewPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); // verify if there is only one folder selected if ((items.count() == 1) && (items.first()->folder())) { QAction* action = new QAction( i18n( "Find/Replace in This Folder..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_contextMenuDirectory = items.at(0)->folder()->path().toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); if ( econtext->view()->selection() ) { QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Find/Replace in Files..."), this); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action); } } if(context->type() == KDevelop::Context::FileContext) { KDevelop::FileContext *fcontext = dynamic_cast(context); // TODO: just stat() or QFileInfo().isDir() for local files? should be faster than mime type checking QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(fcontext->urls().at(0)); static const QMimeType directoryMime = QMimeDatabase().mimeTypeForName(QStringLiteral("inode/directory")); if (mimetype == directoryMime) { QAction* action = new QAction( i18n( "Find/Replace in This Folder..." ), this ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_contextMenuDirectory = fcontext->urls().at(0).toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } return extension; } void GrepViewPlugin::showDialog(bool setLastUsed, QString pattern, bool show) { GrepDialog* dlg = new GrepDialog( this, core()->uiController()->activeMainWindow() ); + GrepJobSettings dlgSettings = dlg->settings(); KDevelop::IDocument* doc = core()->documentController()->activeDocument(); if(!pattern.isEmpty()) { - dlg->setPattern(pattern); + dlgSettings.pattern = pattern; + dlg->setSettings(dlgSettings); } else if(!setLastUsed) { QString pattern = patternFromSelection(doc); if (!pattern.isEmpty()) { - dlg->setPattern( pattern ); + dlgSettings.pattern = pattern; + dlg->setSettings(dlgSettings); } } //if directory is empty then use a default value from the config file. if (!m_directory.isEmpty()) { dlg->setSearchLocations(m_directory); } if(show) dlg->show(); else{ dlg->startSearch(); dlg->deleteLater(); } } void GrepViewPlugin::showDialogFromMenu() { showDialog(); } void GrepViewPlugin::showDialogFromProject() { rememberSearchDirectory(m_contextMenuDirectory); showDialog(); } void GrepViewPlugin::rememberSearchDirectory(QString const & directory) { m_directory = directory; } GrepJob* GrepViewPlugin::newGrepJob() { if(m_currentJob != nullptr) { m_currentJob->kill(); } m_currentJob = new GrepJob(); connect(m_currentJob, &GrepJob::finished, this, &GrepViewPlugin::jobFinished); return m_currentJob; } GrepJob* GrepViewPlugin::grepJob() { return m_currentJob; } void GrepViewPlugin::jobFinished(KJob* job) { if(job == m_currentJob) { emit grepJobFinished(); m_currentJob = nullptr; } } diff --git a/plugins/grepview/tests/test_findreplace.cpp b/plugins/grepview/tests/test_findreplace.cpp index 73227fb212..7c5d91ccd7 100644 --- a/plugins/grepview/tests/test_findreplace.cpp +++ b/plugins/grepview/tests/test_findreplace.cpp @@ -1,195 +1,200 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include #include #include #include #include #include "test_findreplace.h" #include "../grepjob.h" #include "../grepviewplugin.h" #include "../grepoutputmodel.h" void FindReplaceTest::initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); } void FindReplaceTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void FindReplaceTest::testFind_data() { QTest::addColumn("subject"); QTest::addColumn("search"); QTest::addColumn("matches"); QTest::newRow("Basic") << "foobar" << QRegExp("foo") << (MatchList() << Match(0, 0, 3)); QTest::newRow("Multiple matches") << "foobar\nbar\nbarfoo" << QRegExp("foo") << (MatchList() << Match(0, 0, 3) << Match(2, 3, 6)); QTest::newRow("Multiple on same line") << "foobarbaz" << QRegExp("ba") << (MatchList() << Match(0, 3, 5) << Match(0, 6, 8)); QTest::newRow("Multiple sticked together") << "foofoobar" << QRegExp("foo") << (MatchList() << Match(0, 0, 3) << Match(0, 3, 6)); QTest::newRow("RegExp (member call)") << "foo->bar ();\nbar();" << QRegExp("\\->\\s*\\b(bar)\\b\\s*\\(") << (MatchList() << Match(0, 3, 10)); // the matching must be started after the last previous match QTest::newRow("RegExp (greedy match)") << "foofooo" << QRegExp("[o]+") << (MatchList() << Match(0, 1, 3) << Match(0, 4, 7)); QTest::newRow("Matching EOL") << "foobar\nfoobar" << QRegExp("foo.*") << (MatchList() << Match(0, 0, 6) << Match(1, 0, 6)); QTest::newRow("Matching EOL (Windows style)") << "foobar\r\nfoobar" << QRegExp("foo.*") << (MatchList() << Match(0, 0, 6) << Match(1, 0, 6)); QTest::newRow("Empty lines handling") << "foo\n\n\n" << QRegExp("bar") << (MatchList()); QTest::newRow("Can match empty string (at EOL)") << "foobar\n" << QRegExp(".*") << (MatchList() << Match(0, 0, 6)); QTest::newRow("Matching empty string anywhere") << "foobar\n" << QRegExp("") << (MatchList()); } void FindReplaceTest::testFind() { QFETCH(QString, subject); QFETCH(QRegExp, search); QFETCH(MatchList, matches); QTemporaryFile file; QVERIFY(file.open()); file.write(subject.toUtf8()); file.close(); GrepOutputItem::List actualMatches = grepFile(file.fileName(), search); QCOMPARE(actualMatches.length(), matches.length()); for(int i=0; im_range.start().line(), matches[i].line); QCOMPARE(actualMatches[i].change()->m_range.start().column(), matches[i].start); QCOMPARE(actualMatches[i].change()->m_range.end().column(), matches[i].end); } // check that file has not been altered by grepFile QVERIFY(file.open()); QCOMPARE(QString(file.readAll()), subject); } void FindReplaceTest::testReplace_data() { QTest::addColumn("subject"); QTest::addColumn("searchPattern"); QTest::addColumn("searchTemplate"); QTest::addColumn("replace"); QTest::addColumn("replaceTemplate"); QTest::addColumn("result"); QTest::newRow("Raw replace") << (FileList() << File(QStringLiteral("myfile.txt"), QStringLiteral("some text\nreplacement\nsome other test\n")) << File(QStringLiteral("otherfile.txt"), QStringLiteral("some replacement text\n\n"))) << "replacement" << "%s" << "dummy" << "%s" << (FileList() << File(QStringLiteral("myfile.txt"), QStringLiteral("some text\ndummy\nsome other test\n")) << File(QStringLiteral("otherfile.txt"), QStringLiteral("some dummy text\n\n"))); // see bug: https://bugs.kde.org/show_bug.cgi?id=301362 QTest::newRow("LF character replace") << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("hello world\\n"))) << "\\\\n" << "%s" << "\\n\\n" << "%s" << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("hello world\\n\\n"))); QTest::newRow("Template replace") << (FileList() << File(QStringLiteral("somefile.h"), QStringLiteral("struct Foo {\n void setFoo(int foo);\n};")) << File(QStringLiteral("somefile.cpp"), QStringLiteral("instance->setFoo(0);\n setFoo(0); /*not replaced*/"))) << "setFoo" << "\\->\\s*\\b%s\\b\\s*\\(" << "setBar" << "->%s(" << (FileList() << File(QStringLiteral("somefile.h"), QStringLiteral("struct Foo {\n void setFoo(int foo);\n};")) << File(QStringLiteral("somefile.cpp"), QStringLiteral("instance->setBar(0);\n setFoo(0); /*not replaced*/"))); QTest::newRow("Template with captures") << (FileList() << File(QStringLiteral("somefile.cpp"), QStringLiteral("inst::func(1, 2)\n otherInst :: func (\"foo\")\n func()"))) << "func" << "([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(" << "REPL" << "\\1::%s(" << (FileList() << File(QStringLiteral("somefile.cpp"), QStringLiteral("inst::REPL(1, 2)\n otherInst::REPL(\"foo\")\n func()"))); QTest::newRow("Regexp pattern") << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("foobar\n foooobar\n fake"))) << "f\\w*o" << "%s" << "FOO" << "%s" << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("FOObar\n FOObar\n fake"))); } void FindReplaceTest::testReplace() { QFETCH(FileList, subject); QFETCH(QString, searchPattern); QFETCH(QString, searchTemplate); QFETCH(QString, replace); QFETCH(QString, replaceTemplate); QFETCH(FileList, result); QTemporaryDir tempDir; QDir dir(tempDir.path()); // we need some convenience functions that are not in QTemporaryDir foreach(const File& fileData, subject) { QFile file(dir.filePath(fileData.first)); QVERIFY(file.open(QIODevice::WriteOnly)); QVERIFY(file.write(fileData.second.toUtf8()) != -1); file.close(); } GrepJob *job = new GrepJob(this); GrepOutputModel *model = new GrepOutputModel(job); + GrepJobSettings settings; job->setOutputModel(model); - job->setPatternString(searchPattern); - job->setTemplateString(searchTemplate); - job->setReplacementTemplateString(replaceTemplate); - job->setFilesString(QStringLiteral("*")); - job->setExcludeString(QString()); job->setDirectoryChoice(QList() << QUrl::fromLocalFile(dir.path())); - job->setDepth(-1); // fully recursive - job->setRegexpFlag(true); - job->setCaseSensitive(true); - job->setProjectFilesFlag(false); + + settings.projectFilesOnly = false; + settings.caseSensitive = true; + settings.regexp = true; + settings.depth = -1; // fully recursive + settings.pattern = searchPattern; + settings.searchTemplate = searchTemplate; + settings.replacementTemplate = replaceTemplate; + settings.files = QStringLiteral("*"); + settings.exclude = QString(); + + job->setSettings(settings); + QVERIFY(job->exec()); QVERIFY(model->hasResults()); model->setReplacement(replace); model->makeItemsCheckable(true); model->doReplacements(); foreach(const File& fileData, result) { QFile file(dir.filePath(fileData.first)); QVERIFY(file.open(QIODevice::ReadOnly)); QCOMPARE(QString(file.readAll()), fileData.second); file.close(); } tempDir.remove(); } QTEST_MAIN(FindReplaceTest);