File indexing completed on 2024-04-21 04:35:56

0001 /* This file is part of KDevelop
0002  *
0003  * Copyright (C) 2014-2015 Miquel Sabaté Solà <mikisabate@gmail.com>
0004  *
0005  * This program is free software: you can redistribute it and/or modify
0006  * it under the terms of the GNU General Public License as published by
0007  * the Free Software Foundation, either version 3 of the License, or
0008  * (at your option) any later version.
0009  *
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License
0016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017  */
0018 
0019 #include <launcher.h>
0020 
0021 #include <QtWidgets/QAction>
0022 
0023 #include <KConfigGroup>
0024 #include <KLocalizedString>
0025 
0026 #include <execute/iexecuteplugin.h>
0027 #include <interfaces/icore.h>
0028 #include <interfaces/idocumentcontroller.h>
0029 #include <interfaces/iruncontroller.h>
0030 #include <interfaces/ilanguagecontroller.h>
0031 #include <interfaces/ilaunchconfiguration.h>
0032 #include <interfaces/ilauncher.h>
0033 #include <interfaces/ilaunchmode.h>
0034 #include <interfaces/iplugincontroller.h>
0035 #include <interfaces/launchconfigurationtype.h>
0036 #include <language/duchain/duchainlock.h>
0037 #include <language/duchain/duchainutils.h>
0038 #include <language/duchain/topducontext.h>
0039 
0040 #include <debug.h>
0041 #include <languagesupport.h>
0042 #include <rails/helpers.h>
0043 
0044 using namespace ruby;
0045 using namespace KDevelop;
0046 
0047 namespace {
0048 
0049 /**
0050  * @internal Set up the launch configuration before the run occurs.
0051  * @param cfg the KConfigGroup for this launch.
0052  * @param document the currently active document.
0053  */
0054 void setupBeforeRun(KConfigGroup &cfg, IDocument *document)
0055 {
0056     Path root = rails::Helpers::findRailsRoot(document->url());
0057 
0058     if (root.isValid()) {
0059         cfg.writeEntry("Working Directory", root.toLocalFile());
0060     } else {
0061         cfg.writeEntry("Working Directory", root.parent().path());
0062     }
0063 }
0064 
0065 /**
0066  * @internal Find the method under the cursor in the given \p doc. It's
0067  * used by the runCurrentTestFunction() slot.
0068  */
0069 QString findFunctionUnderCursor(KDevelop::IDocument *doc)
0070 {
0071     DUChainReadLocker lock;
0072 
0073     const auto top = DUChainUtils::standardContextForUrl(doc->url());
0074     if (!top) {
0075         return "";
0076     }
0077 
0078     const auto &cursor = doc->cursorPosition();
0079     auto context = top->findContextAt(CursorInRevision(
0080         cursor.line(), cursor.column()
0081     ));
0082     if (!context) {
0083         return "";
0084     }
0085 
0086     qCDebug(KDEV_RUBY) << "CONTEXT ID" << context->localScopeIdentifier();
0087     return context->localScopeIdentifier().toString();
0088 }
0089 
0090 }
0091 
0092 Launcher::Launcher(LanguageSupport *support)
0093     : QObject(support)
0094     , m_file(nullptr)
0095     , m_function(nullptr)
0096 {
0097     m_support = support;
0098 }
0099 
0100 void Launcher::setupActions(KActionCollection &actions)
0101 {
0102     QAction *action = actions.addAction("ruby_run_current_file");
0103     action->setText(i18n("Run Current File"));
0104     action->setShortcut(Qt::META | Qt::Key_F9);
0105     connect(action, &QAction::triggered, this, &Launcher::runCurrentFile);
0106 
0107     action = actions.addAction("ruby_run_current_test_function");
0108     action->setText(i18n("Run Current Test Function"));
0109     action->setShortcut(Qt::META | Qt::SHIFT | Qt::Key_F9);
0110     connect(action, &QAction::triggered, this, &Launcher::runCurrentTest);
0111 }
0112 
0113 ILaunchConfiguration * Launcher::launchConfiguration(const QString &name)
0114 {
0115     for (auto config : m_support->core()->runController()->launchConfigurations()) {
0116         if (config->name() == name) {
0117             return config;
0118         }
0119     }
0120 
0121     auto executePlugin = m_support->core()->pluginController()->
0122         pluginForExtension("org.kdevelop.IExecutePlugin")->extension<IExecutePlugin>();
0123     auto type = m_support->core()->runController()->launchConfigurationTypeForId(
0124         executePlugin->nativeAppConfigTypeId());
0125 
0126     if (!type) {
0127         return nullptr;
0128     }
0129 
0130     auto mode = m_support->core()->runController()->launchModeForId("execute");
0131     if (!mode) {
0132         return nullptr;
0133     }
0134 
0135     KDevelop::ILauncher *launcher = nullptr;
0136     for (auto l : type->launchers()) {
0137         if (l->supportedModes().contains(QStringLiteral("execute"))) {
0138             launcher = l;
0139             break;
0140         }
0141     }
0142     if (!launcher) {
0143         return nullptr;
0144     }
0145 
0146     auto config = m_support->core()->runController()->createLaunchConfiguration(
0147         type, qMakePair(mode->id(), launcher->id()), nullptr, name);
0148 
0149     KConfigGroup cfg = config->config();
0150     cfg.writeEntry("isExecutable", true);
0151     cfg.writeEntry("Executable", "ruby");
0152     cfg.sync();
0153 
0154     return config;
0155 }
0156 
0157 void Launcher::runCurrentFile()
0158 {
0159     auto doc = ICore::self()->documentController()->activeDocument();
0160     if (!doc) {
0161         return;
0162     }
0163 
0164     // Get out if this is not a Ruby file.
0165     if (!ICore::self()->languageController()->languagesForUrl(doc->url()).
0166             contains(m_support)) {
0167         return;
0168     }
0169 
0170     if (!m_file) {
0171         m_file = launchConfiguration(i18n("Current Ruby File"));
0172         if (!m_file) {
0173             return;
0174         }
0175     }
0176 
0177     KConfigGroup cfg = m_file->config();
0178     setupBeforeRun(cfg, doc);
0179     cfg.writeEntry("Arguments", QStringList() << doc->url().toLocalFile());
0180     cfg.sync();
0181 
0182     m_support->core()->runController()->
0183         execute(QStringLiteral("execute"), m_file);
0184 }
0185 
0186 void Launcher::runCurrentTest()
0187 {
0188     auto doc = ICore::self()->documentController()->activeDocument();
0189     if (!doc) {
0190         return;
0191     }
0192 
0193     // Get out if this is not a Ruby file.
0194     if (!ICore::self()->languageController()->languagesForUrl(doc->url()).
0195             contains(m_support)) {
0196         return;
0197     }
0198 
0199     if (!m_function) {
0200         m_function = launchConfiguration(i18n("Current Ruby Test Function"));
0201         if (!m_function) {
0202             return;
0203         }
0204     }
0205 
0206     // Find function under the cursor (if any).
0207     const QString &&currentFunction = findFunctionUnderCursor(doc);
0208     if (currentFunction.isEmpty()) {
0209         return;
0210     }
0211 
0212     KConfigGroup cfg = m_function->config();
0213     setupBeforeRun(cfg, doc);
0214     QStringList args{
0215         doc->url().toLocalFile(),
0216         "-n",
0217         currentFunction
0218     };
0219     cfg.writeEntry("Arguments", args.join(" "));
0220     cfg.sync();
0221 
0222     m_support->core()->runController()->execute(
0223         QStringLiteral("execute"), m_function);
0224 }
0225