File indexing completed on 2024-04-28 04:38:54

0001 /*
0002     SPDX-FileCopyrightText: 1999-2001 Bernd Gehrmann <bernd@kdevelop.org>
0003     SPDX-FileCopyrightText: 2007 Dukju Ahn <dukjuahn@gmail.com>
0004     SPDX-FileCopyrightText: 2010 Benjamin Port <port.benjamin@gmail.com>
0005     SPDX-FileCopyrightText: 2010 Julien Desgats <julien.desgats@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "grepviewplugin.h"
0011 #include "grepdialog.h"
0012 #include "grepoutputmodel.h"
0013 #include "grepoutputdelegate.h"
0014 #include "grepjob.h"
0015 #include "grepoutputview.h"
0016 #include "debug.h"
0017 
0018 #include <QAction>
0019 #include <QDBusConnection>
0020 #include <QKeySequence>
0021 #include <QMimeDatabase>
0022 
0023 #include <KActionCollection>
0024 #include <KLocalizedString>
0025 #include <KParts/MainWindow>
0026 #include <KTextEditor/Document>
0027 #include <KTextEditor/View>
0028 
0029 #include <interfaces/icore.h>
0030 #include <interfaces/iuicontroller.h>
0031 #include <interfaces/idocument.h>
0032 #include <interfaces/idocumentcontroller.h>
0033 #include <interfaces/iproject.h>
0034 #include <interfaces/contextmenuextension.h>
0035 #include <project/projectmodel.h>
0036 #include <util/path.h>
0037 #include <language/interfaces/editorcontext.h>
0038 
0039 #include <utility>
0040 
0041 static QString patternFromSelection(const KDevelop::IDocument* doc)
0042 {
0043     if (!doc)
0044         return QString();
0045 
0046     QString pattern;
0047     KTextEditor::Range range = doc->textSelection();
0048     if( range.isValid() )
0049     {
0050         pattern = doc->textDocument()->text( range );
0051     }
0052     if( pattern.isEmpty() )
0053     {
0054         pattern = doc->textWord();
0055     }
0056 
0057     // Before anything, this removes line feeds from the
0058     // beginning and the end.
0059     int len = pattern.length();
0060     if (len > 0 && pattern[0] == QLatin1Char('\n')) {
0061         pattern.remove(0, 1);
0062         len--;
0063     }
0064     if (len > 0 && pattern[len-1] == QLatin1Char('\n'))
0065         pattern.truncate(len-1);
0066     return pattern;
0067 }
0068 
0069 GrepViewPlugin::GrepViewPlugin( QObject *parent, const QVariantList & )
0070     : KDevelop::IPlugin( QStringLiteral("kdevgrepview"), parent ), m_currentJob(nullptr)
0071 {
0072     setXMLFile(QStringLiteral("kdevgrepview.rc"));
0073 
0074     QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/GrepViewPlugin"),
0075         this, QDBusConnection::ExportScriptableSlots );
0076 
0077     QAction*action = actionCollection()->addAction(QStringLiteral("edit_grep"));
0078     action->setText(i18nc("@action", "Find/Replace in Fi&les..."));
0079     actionCollection()->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Alt+F")) );
0080     connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu);
0081     action->setToolTip( i18nc("@info:tooltip", "Search for expressions over several files") );
0082     action->setWhatsThis( i18nc("@info:whatsthis",
0083                                "Opens the 'Find/Replace in Files' dialog. There you "
0084                                "can enter a regular expression which is then "
0085                                "searched for within all files in the directories "
0086                                "you specify. Matches will be displayed, you "
0087                                "can switch to a match directly. You can also do replacement.") );
0088     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find")));
0089 
0090     // instantiate delegate, it's supposed to be deleted via QObject inheritance
0091     new GrepOutputDelegate(this);
0092     m_factory = new GrepOutputViewFactory(this);
0093     core()->uiController()->addToolView(i18nc("@title:window", "Find/Replace in Files"), m_factory);
0094 }
0095 
0096 GrepOutputViewFactory* GrepViewPlugin::toolViewFactory() const
0097 {
0098     return m_factory;
0099 }
0100 
0101 GrepViewPlugin::~GrepViewPlugin()
0102 {
0103 }
0104 
0105 void GrepViewPlugin::unload()
0106 {
0107     for (const QPointer<GrepDialog>& p : qAsConst(m_currentDialogs)) {
0108         if (p) {
0109             p->reject();
0110             p->deleteLater();
0111         }
0112     }
0113 
0114     core()->uiController()->removeToolView(m_factory);
0115 }
0116 
0117 void GrepViewPlugin::startSearch(const QString& pattern, const QString& directory, bool show)
0118 {
0119     m_directory = directory;
0120     showDialog(false, pattern, show);
0121 }
0122 
0123 KDevelop::ContextMenuExtension GrepViewPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
0124 {
0125     KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context, parent);
0126     if( context->type() == KDevelop::Context::ProjectItemContext ) {
0127         auto* ctx = static_cast<KDevelop::ProjectItemContext*>(context);
0128         QList<KDevelop::ProjectBaseItem*> items = ctx->items();
0129         // verify if there is only one folder selected
0130         if ((items.count() == 1) && (items.first()->folder())) {
0131             auto* action = new QAction(i18nc("@action:inmenu", "Find/Replace in This Folder..."), parent);
0132             action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find")));
0133             m_contextMenuDirectory = items.at(0)->folder()->path().toLocalFile();
0134             connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject);
0135             extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action );
0136         }
0137     }
0138 
0139     if ( context->type() == KDevelop::Context::EditorContext ) {
0140         auto* econtext = static_cast<KDevelop::EditorContext*>(context);
0141         if ( econtext->view()->selection() ) {
0142             auto* action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18nc("@action:inmenu", "&Find/Replace in Files..."), parent);
0143             connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu);
0144             extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action);
0145         }
0146     }
0147 
0148     if(context->type() == KDevelop::Context::FileContext) {
0149         auto* fcontext = static_cast<KDevelop::FileContext*>(context);
0150         // TODO: just stat() or QFileInfo().isDir() for local files? should be faster than mime type checking
0151         QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(fcontext->urls().at(0));
0152         static const QMimeType directoryMime = QMimeDatabase().mimeTypeForName(QStringLiteral("inode/directory"));
0153         if (mimetype == directoryMime) {
0154             auto* action = new QAction(i18nc("@action:inmenu", "Find/Replace in This Folder..."), parent);
0155             action->setIcon(QIcon::fromTheme(QStringLiteral("edit-find")));
0156             m_contextMenuDirectory = fcontext->urls().at(0).toLocalFile();
0157             connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject);
0158             extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action );
0159         }
0160     }
0161     return extension;
0162 }
0163 
0164 void GrepViewPlugin::showDialog(bool setLastUsed, const QString& pattern, bool show)
0165 {
0166     // check if dialog pointers are still valid, remove them otherwise
0167     m_currentDialogs.removeAll(QPointer<GrepDialog>());
0168 
0169     auto* const dlg = new GrepDialog(this, nullptr, core()->uiController()->activeMainWindow(), show);
0170     m_currentDialogs << dlg;
0171 
0172     if (!show) {
0173         // The UI is uninitialized, so the settings must be read from config.
0174         dlg->setLastUsedSettings();
0175     }
0176 
0177     if(!pattern.isEmpty())
0178     {
0179         dlg->setPattern(pattern);
0180     }
0181     else if(!setLastUsed)
0182     {
0183         QString pattern = patternFromSelection(core()->documentController()->activeDocument());
0184         if (!pattern.isEmpty()) {
0185             dlg->setPattern(std::move(pattern));
0186         }
0187     }
0188 
0189     //if directory is empty then use a default value from the config file.
0190     if (!m_directory.isEmpty()) {
0191         dlg->setSearchLocations(m_directory);
0192     }
0193 
0194     if(show)
0195         dlg->show();
0196     else{
0197         dlg->startSearch();
0198         dlg->deleteLater();
0199     }
0200 }
0201 
0202 void GrepViewPlugin::showDialogFromMenu()
0203 {
0204     showDialog();
0205 }
0206 
0207 void GrepViewPlugin::showDialogFromProject()
0208 {
0209     rememberSearchDirectory(m_contextMenuDirectory);
0210     showDialog();
0211 }
0212 
0213 void GrepViewPlugin::rememberSearchDirectory(QString const & directory)
0214 {
0215     m_directory = directory;
0216 }
0217 
0218 GrepJob* GrepViewPlugin::newGrepJob()
0219 {
0220     if(m_currentJob != nullptr)
0221     {
0222         m_currentJob->kill();
0223     }
0224     m_currentJob = new GrepJob();
0225     connect(m_currentJob, &GrepJob::finished, this, &GrepViewPlugin::jobFinished);
0226     return m_currentJob;
0227 }
0228 
0229 GrepJob* GrepViewPlugin::grepJob()
0230 {
0231     return m_currentJob;
0232 }
0233 
0234 void GrepViewPlugin::jobFinished(KJob* job)
0235 {
0236     if(job == m_currentJob)
0237     {
0238         m_currentJob = nullptr;
0239         emit grepJobFinished(job->error() == KJob::NoError);
0240     }
0241 }
0242 
0243 #include "moc_grepviewplugin.cpp"