diff --git a/plugins/bazaar/bazaarplugin.cpp b/plugins/bazaar/bazaarplugin.cpp
index 944dcc4fef..791a07c6d4 100644
--- a/plugins/bazaar/bazaarplugin.cpp
+++ b/plugins/bazaar/bazaarplugin.cpp
@@ -1,351 +1,350 @@
/***************************************************************************
* Copyright 2013-2014 Maciej Poleski *
* *
* 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "bazaarplugin.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bazaarutils.h"
#include "bzrannotatejob.h"
#include "copyjob.h"
#include "diffjob.h"
using namespace KDevelop;
BazaarPlugin::BazaarPlugin(QObject* parent, const QVariantList& args) :
IPlugin(QStringLiteral("kdevbazaar"), parent),
m_vcsPluginHelper(new KDevelop::VcsPluginHelper(this, this)), m_hasError(false)
{
Q_UNUSED(args); // What is this?
if (QStandardPaths::findExecutable(QStringLiteral("bzr")).isEmpty()) {
m_hasError = true;
m_errorDescription = i18n("Bazaar is not installed (bzr executable not"
" found)");
return;
}
KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl)
KDEV_USE_EXTENSION_INTERFACE(KDevelop::IDistributedVersionControl)
setObjectName(QStringLiteral("Bazaar"));
}
BazaarPlugin::~BazaarPlugin()
{
}
QString BazaarPlugin::name() const
{
return QStringLiteral("Bazaar");
}
VcsJob* BazaarPlugin::add(const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
job->setType(VcsJob::Add);
*job << "bzr" << "add";
if(recursion == NonRecursive)
*job << "--no-recurse";
*job << localLocations;
return job;
}
VcsJob* BazaarPlugin::annotate(const QUrl& localLocation, const VcsRevision& rev)
{
VcsJob* job = new BzrAnnotateJob(BazaarUtils::workingCopy(localLocation), BazaarUtils::getRevisionSpec(rev), localLocation, this, KDevelop::OutputJob::Silent);
return job;
}
VcsJob* BazaarPlugin::commit(const QString& message, const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
QDir dir = BazaarUtils::workingCopy(localLocations[0]);
DVcsJob* job = new DVcsJob(dir, this);
job->setType(VcsJob::Commit);
*job << "bzr" << "commit" << BazaarUtils::handleRecursion(localLocations, recursion) << "-m" << message;
return job;
}
VcsJob* BazaarPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn)
{
return new CopyJob(localLocationSrc, localLocationDstn, this);
}
VcsImportMetadataWidget* BazaarPlugin::createImportMetadataWidget(QWidget* parent)
{
return new DvcsImportMetadataWidget(parent);
}
VcsJob* BazaarPlugin::createWorkingCopy(const VcsLocation& sourceRepository, const QUrl& destinationDirectory, IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(recursion);
// What is the purpose of recursion parameter?
DVcsJob* job = new DVcsJob(BazaarUtils::toQDir(sourceRepository.localUrl()), this);
job->setType(VcsJob::Import);
*job << "bzr" << "branch" << sourceRepository.localUrl().url() << destinationDirectory;
return job;
}
VcsJob* BazaarPlugin::diff(const QUrl& fileOrDirectory, const VcsRevision& srcRevision, const VcsRevision& dstRevision, VcsDiff::Type, IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(recursion);
VcsJob* job = new DiffJob(BazaarUtils::workingCopy(fileOrDirectory), BazaarUtils::getRevisionSpecRange(srcRevision, dstRevision), fileOrDirectory, this);
return job;
}
VcsJob* BazaarPlugin::init(const QUrl& localRepositoryRoot)
{
DVcsJob* job = new DVcsJob(BazaarUtils::toQDir(localRepositoryRoot), this);
job->setType(VcsJob::Import);
*job << "bzr" << "init";
return job;
}
bool BazaarPlugin::isVersionControlled(const QUrl& localLocation)
{
QDir workCopy = BazaarUtils::workingCopy(localLocation);
DVcsJob* job = new DVcsJob(workCopy, this, OutputJob::Silent);
job->setType(VcsJob::Unknown);
job->setIgnoreError(true);
*job << "bzr" << "ls" << "--from-root" << "-R" << "-V";
job->exec();
if (job->status() == VcsJob::JobSucceeded) {
QList filesAndDirectoriesList;
foreach (const QString& fod, job->output().split('\n')) {
filesAndDirectoriesList.append(QFileInfo(workCopy.absolutePath() + QDir::separator() + fod));
}
QFileInfo fi(localLocation.toLocalFile());
if (fi.isDir() || fi.isFile()) {
QFileInfo file(localLocation.toLocalFile());
return filesAndDirectoriesList.contains(file);
}
}
return false;
}
VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, long unsigned int limit)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
job->setType(VcsJob::Log);
*job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(rev) << "-l" << QString::number(limit);
connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog);
return job;
}
VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, const VcsRevision& limit)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
job->setType(VcsJob::Log);
*job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(limit, rev);
connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog);
return job;
}
void BazaarPlugin::parseBzrLog(DVcsJob* job)
{
QVariantList result;
auto parts = job->output().split(QStringLiteral("------------------------------------------------------------"), QString::SkipEmptyParts);
foreach (const QString& part, parts) {
auto event = BazaarUtils::parseBzrLogPart(part);
if (event.revision().revisionType() != VcsRevision::Invalid)
result.append(QVariant::fromValue(event));
}
job->setResults(result);
}
VcsJob* BazaarPlugin::move(const QUrl& localLocationSrc, const QUrl& localLocationDst)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocationSrc), this);
job->setType(VcsJob::JobType::Move);
*job << "bzr" << "move" << localLocationSrc << localLocationDst;
return job;
}
VcsJob* BazaarPlugin::pull(const VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation)
{
// API describes hg pull which is git fetch equivalent
// bzr has pull, but it succeeds only if fast-forward is possible
// in other cases bzr merge should be used instead (bzr pull would fail)
// Information about repository must be provided at least once.
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this);
job->setType(VcsJob::JobType::Pull);
*job << "bzr" << "pull";
if (!localOrRepoLocationSrc.localUrl().isEmpty()) {
*job << localOrRepoLocationSrc.localUrl();
}
// localUrl always makes sense. Even on remote repositores which are handled
// transparently.
return job;
}
VcsJob* BazaarPlugin::push(const QUrl& localRepositoryLocation, const VcsLocation& localOrRepoLocationDst)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this);
job->setType(VcsJob::JobType::Push);
*job << "bzr" << "push" << localOrRepoLocationDst.localUrl();
// localUrl always makes sense. Even on remote repositores which are handled
// transparently.
return job;
}
VcsJob* BazaarPlugin::remove(const QList& localLocations)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
job->setType(VcsJob::JobType::Remove);
*job << "bzr" << "remove" << localLocations;
return job;
}
VcsJob* BazaarPlugin::repositoryLocation(const QUrl& localLocation)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
job->setType(VcsJob::JobType::Unknown);
*job << "bzr" << "root" << localLocation; // It is only to make sure
connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrRoot);
return job;
}
void BazaarPlugin::parseBzrRoot(DVcsJob* job)
{
QString filename = job->dvcsCommand().at(2);
QString rootDirectory = job->output();
QString localFilename = QFileInfo(QUrl::fromLocalFile(filename).toLocalFile()).absoluteFilePath();
- QString localRootDirectory = QFileInfo(rootDirectory).absolutePath();
QString result = localFilename.mid(localFilename.indexOf(rootDirectory) + rootDirectory.length());
job->setResults(QVariant::fromValue(result));
}
VcsJob* BazaarPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
return add(localLocations, recursion);
// How to provide "a conflict solving dialog to the user"?
// In any case this plugin is unable to make any conflict.
}
VcsJob* BazaarPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
job->setType(VcsJob::JobType::Revert);
*job << "bzr" << "revert" << BazaarUtils::handleRecursion(localLocations, recursion);
return job;
}
VcsJob* BazaarPlugin::status(const QList& localLocations, IBasicVersionControl::RecursionMode recursion)
{
Q_UNUSED(recursion);
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
job->setType(VcsJob::Status);
*job << "bzr" << "status" << "--short" << "--no-pending" << "--no-classify" << localLocations;
connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrStatus);
return job;
}
void BazaarPlugin::parseBzrStatus(DVcsJob* job)
{
QVariantList result;
QSet filesWithStatus;
QDir workingCopy = job->directory();
foreach (const QString& line, job->output().split('\n')) {
auto status = BazaarUtils::parseVcsStatusInfoLine(line);
result.append(QVariant::fromValue(status));
filesWithStatus.insert(BazaarUtils::concatenatePath(workingCopy, status.url()));
}
QStringList command = job->dvcsCommand();
for (auto it = command.constBegin() + command.indexOf(QStringLiteral("--no-classify")) + 1, itEnd = command.constEnd(); it != itEnd; ++it) {
QString path = QFileInfo(*it).absoluteFilePath();
if (!filesWithStatus.contains(path)) {
filesWithStatus.insert(path);
KDevelop::VcsStatusInfo status;
status.setState(VcsStatusInfo::ItemUpToDate);
status.setUrl(QUrl::fromLocalFile(*it));
result.append(QVariant::fromValue(status));
}
}
job->setResults(result);
}
VcsJob* BazaarPlugin::update(const QList& localLocations, const VcsRevision& rev, IBasicVersionControl::RecursionMode recursion)
{
// bzr update is stronger than API (it's effectively merge)
// the best approximation is bzr pull
DVcsJob* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
Q_UNUSED(recursion);
// recursion and file locations are ignored - we can update only whole
// working copy
job->setType(VcsJob::JobType::Update);
*job << "bzr" << "pull" << BazaarUtils::getRevisionSpec(rev);
return job;
}
VcsLocationWidget* BazaarPlugin::vcsLocation(QWidget* parent) const
{
return new KDevelop::StandardVcsLocationWidget(parent);
}
ContextMenuExtension BazaarPlugin::contextMenuExtension(Context* context)
{
m_vcsPluginHelper->setupFromContext(context);
QList const& ctxUrlList = m_vcsPluginHelper->contextUrlList();
bool isWorkingDirectory = false;
for (const QUrl & url : ctxUrlList) {
if (BazaarUtils::isValidDirectory(url)) {
isWorkingDirectory = true;
break;
}
}
if (!isWorkingDirectory) { // Not part of a repository
return ContextMenuExtension();
}
QMenu* menu = m_vcsPluginHelper->commonActions();
ContextMenuExtension menuExt;
menuExt.addAction(ContextMenuExtension::VcsGroup, menu->menuAction());
return menuExt;
}
bool BazaarPlugin::hasError() const
{
return m_hasError;
}
QString BazaarPlugin::errorDescription() const
{
return m_errorDescription;
}
diff --git a/plugins/git/tests/test_git.cpp b/plugins/git/tests/test_git.cpp
index 3cfb4b274c..7918d90525 100644
--- a/plugins/git/tests/test_git.cpp
+++ b/plugins/git/tests/test_git.cpp
@@ -1,491 +1,490 @@
/***************************************************************************
* This file was partly taken from KDevelop's cvs plugin *
* Copyright 2007 Robert Gruber *
* *
* Adapted for Git *
* Copyright 2008 Evgeniy Ivanov *
* *
* 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "test_git.h"
#include
#include
#include
#include
#include
#include
#include
#include "../gitplugin.h"
#define VERIFYJOB(j) \
do { QVERIFY(j); QVERIFY(j->exec()); QVERIFY((j)->status() == KDevelop::VcsJob::JobSucceeded); } while(0)
inline QString tempDir() { return QDir::tempPath(); }
inline QString gitTest_BaseDir() { return tempDir() + "/kdevGit_testdir/"; }
inline QString gitTest_BaseDir2() { return tempDir() + "/kdevGit_testdir2/"; }
inline QString gitRepo() { return gitTest_BaseDir() + ".git"; }
inline QString gitSrcDir() { return gitTest_BaseDir() + "src/"; }
inline QString gitTest_FileName() { return QStringLiteral("testfile"); }
inline QString gitTest_FileName2() { return QStringLiteral("foo"); }
inline QString gitTest_FileName3() { return QStringLiteral("bar"); }
using namespace KDevelop;
bool writeFile(const QString &path, const QString& content, QIODevice::OpenModeFlag mode = QIODevice::WriteOnly)
{
QFile f(path);
if (!f.open(mode)) {
return false;
}
QTextStream input(&f);
input << content;
return true;
}
void GitInitTest::initTestCase()
{
AutoTestShell::init({QStringLiteral("kdevgit")});
TestCore::initialize();
m_plugin = new GitPlugin(TestCore::self());
}
void GitInitTest::cleanupTestCase()
{
delete m_plugin;
TestCore::shutdown();
}
void GitInitTest::init()
{
// Now create the basic directory structure
QDir tmpdir(tempDir());
tmpdir.mkdir(gitTest_BaseDir());
tmpdir.mkdir(gitSrcDir());
tmpdir.mkdir(gitTest_BaseDir2());
}
void GitInitTest::cleanup()
{
removeTempDirs();
}
void GitInitTest::repoInit()
{
qDebug() << "Trying to init repo";
// make job that creates the local repository
VcsJob* j = m_plugin->init(QUrl::fromLocalFile(gitTest_BaseDir()));
VERIFYJOB(j);
//check if the CVSROOT directory in the new local repository exists now
QVERIFY(QFileInfo::exists(gitRepo()));
//check if isValidDirectory works
QVERIFY(m_plugin->isValidDirectory(QUrl::fromLocalFile(gitTest_BaseDir())));
//and for non-git dir, I hope nobody has /tmp under git
QVERIFY(!m_plugin->isValidDirectory(QUrl::fromLocalFile(QStringLiteral("/tmp"))));
//we have nothing, so output should be empty
DVcsJob * j2 = m_plugin->gitRevParse(gitRepo(), QStringList(QStringLiteral("--branches")));
QVERIFY(j2);
QVERIFY(j2->exec());
- QString out = j2->output();
QVERIFY(j2->output().isEmpty());
// Make sure to set the Git identity so unit tests don't depend on that
auto j3 = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()),
QStringLiteral("user.email"), QStringLiteral("me@example.com"));
VERIFYJOB(j3);
auto j4 = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()),
QStringLiteral("user.name"), QStringLiteral("My Name"));
VERIFYJOB(j4);
}
void GitInitTest::addFiles()
{
qDebug() << "Adding files to the repo";
//we start it after repoInit, so we still have empty git repo
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("HELLO WORLD")));
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName2(), QStringLiteral("No, bar()!")));
//test git-status exitCode (see DVcsJob::setExitCode).
VcsJob* j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir()));
VERIFYJOB(j);
// /tmp/kdevGit_testdir/ and testfile
j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName()));
VERIFYJOB(j);
QVERIFY(writeFile(gitSrcDir() + gitTest_FileName3(), QStringLiteral("No, foo()! It's bar()!")));
//test git-status exitCode again
j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir()));
VERIFYJOB(j);
//repository path without trailing slash and a file in a parent directory
// /tmp/repo and /tmp/repo/src/bar
j = m_plugin->add(QList() << QUrl::fromLocalFile(gitSrcDir() + gitTest_FileName3()));
VERIFYJOB(j);
//let's use absolute path, because it's used in ContextMenus
j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName2()));
VERIFYJOB(j);
//Now let's create several files and try "git add file1 file2 file3"
QStringList files = QStringList() << QStringLiteral("file1") << QStringLiteral("file2") << QStringLiteral("la la");
QList multipleFiles;
foreach(const QString& file, files) {
QVERIFY(writeFile(gitTest_BaseDir() + file, file));
multipleFiles << QUrl::fromLocalFile(gitTest_BaseDir() + file);
}
j = m_plugin->add(multipleFiles);
VERIFYJOB(j);
}
void GitInitTest::commitFiles()
{
qDebug() << "Committing...";
//we start it after addFiles, so we just have to commit
VcsJob* j = m_plugin->commit(QStringLiteral("Test commit"), QList() << QUrl::fromLocalFile(gitTest_BaseDir()));
VERIFYJOB(j);
//test git-status exitCode one more time.
j = m_plugin->status(QList() << QUrl::fromLocalFile(gitTest_BaseDir()));
VERIFYJOB(j);
//since we committed the file to the "pure" repository, .git/refs/heads/master should exist
//TODO: maybe other method should be used
QString headRefName(gitRepo() + "/refs/heads/master");
QVERIFY(QFileInfo::exists(headRefName));
//Test the results of the "git add"
DVcsJob* jobLs = new DVcsJob(gitTest_BaseDir(), m_plugin);
*jobLs << "git" << "ls-tree" << "--name-only" << "-r" << "HEAD";
if (jobLs->exec() && jobLs->status() == KDevelop::VcsJob::JobSucceeded) {
QStringList files = jobLs->output().split('\n');
QVERIFY(files.contains(gitTest_FileName()));
QVERIFY(files.contains(gitTest_FileName2()));
QVERIFY(files.contains("src/" + gitTest_FileName3()));
}
QString firstCommit;
QFile headRef(headRefName);
if (headRef.open(QIODevice::ReadOnly)) {
QTextStream output(&headRef);
output >> firstCommit;
}
headRef.close();
QVERIFY(!firstCommit.isEmpty());
qDebug() << "Committing one more time";
//let's try to change the file and test "git commit -a"
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("Just another HELLO WORLD\n")));
//add changes
j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName()));
VERIFYJOB(j);
j = m_plugin->commit(QStringLiteral("KDevelop's Test commit2"), QList() << QUrl::fromLocalFile(gitTest_BaseDir()));
VERIFYJOB(j);
QString secondCommit;
if (headRef.open(QIODevice::ReadOnly)) {
QTextStream output(&headRef);
output >> secondCommit;
}
headRef.close();
QVERIFY(!secondCommit.isEmpty());
QVERIFY(firstCommit != secondCommit);
}
void GitInitTest::testInit()
{
repoInit();
}
static QString runCommand(const QString& cmd, const QStringList& args)
{
QProcess proc;
proc.setWorkingDirectory(gitTest_BaseDir());
proc.start(cmd, args);
proc.waitForFinished();
return proc.readAllStandardOutput().trimmed();
}
void GitInitTest::testReadAndSetConfigOption()
{
repoInit();
{
qDebug() << "read non-existing config option";
QString nameFromPlugin = m_plugin->readConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()),
QStringLiteral("notexisting.asdads"));
QVERIFY(nameFromPlugin.isEmpty());
}
{
qDebug() << "write user.name = \"John Tester\"";
auto job = m_plugin->setConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()),
QStringLiteral("user.name"), QStringLiteral("John Tester"));
VERIFYJOB(job);
const auto name = runCommand("git", {"config", "--get", QStringLiteral("user.name")});
QCOMPARE(name, QStringLiteral("John Tester"));
}
{
qDebug() << "read user.name";
const QString nameFromPlugin = m_plugin->readConfigOption(QUrl::fromLocalFile(gitTest_BaseDir()),
QStringLiteral("user.name"));
QCOMPARE(nameFromPlugin, QStringLiteral("John Tester"));
const auto name = runCommand("git", {"config", "--get", QStringLiteral("user.name")});
QCOMPARE(name, QStringLiteral("John Tester"));
}
}
void GitInitTest::testAdd()
{
repoInit();
addFiles();
}
void GitInitTest::testCommit()
{
repoInit();
addFiles();
commitFiles();
}
void GitInitTest::testBranch(const QString& newBranch)
{
//Already tested, so I assume that it works
const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir());
QString oldBranch = runSynchronously(m_plugin->currentBranch(baseUrl)).toString();
VcsRevision rev;
rev.setRevisionValue(oldBranch, KDevelop::VcsRevision::GlobalNumber);
VcsJob* j = m_plugin->branch(baseUrl, rev, newBranch);
VERIFYJOB(j);
QVERIFY(runSynchronously(m_plugin->branches(baseUrl)).toStringList().contains(newBranch));
// switch branch
j = m_plugin->switchBranch(baseUrl, newBranch);
VERIFYJOB(j);
QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), newBranch);
// get into detached head state
j = m_plugin->switchBranch(baseUrl, QStringLiteral("HEAD~1"));
VERIFYJOB(j);
QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), QString());
// switch back
j = m_plugin->switchBranch(baseUrl, newBranch);
VERIFYJOB(j);
QCOMPARE(runSynchronously(m_plugin->currentBranch(baseUrl)).toString(), newBranch);
j = m_plugin->deleteBranch(baseUrl, oldBranch);
VERIFYJOB(j);
QVERIFY(!runSynchronously(m_plugin->branches(baseUrl)).toStringList().contains(oldBranch));
}
void GitInitTest::testBranching()
{
repoInit();
addFiles();
commitFiles();
const QUrl baseUrl = QUrl::fromLocalFile(gitTest_BaseDir());
VcsJob* j = m_plugin->branches(baseUrl);
VERIFYJOB(j);
QString curBranch = runSynchronously(m_plugin->currentBranch(baseUrl)).toString();
QCOMPARE(curBranch, QStringLiteral("master"));
testBranch(QStringLiteral("new"));
testBranch(QStringLiteral("averylongbranchnamejusttotestlongnames"));
testBranch(QStringLiteral("KDE/4.10"));
}
void GitInitTest::revHistory()
{
repoInit();
addFiles();
commitFiles();
QList commits = m_plugin->getAllCommits(gitTest_BaseDir());
QVERIFY(!commits.isEmpty());
QStringList logMessages;
for (int i = 0; i < commits.count(); ++i)
logMessages << commits[i].getLog();
QCOMPARE(commits.count(), 2);
QCOMPARE(logMessages[0], QStringLiteral("KDevelop's Test commit2")); //0 is later than 1!
QCOMPARE(logMessages[1], QStringLiteral("Test commit"));
QVERIFY(commits[1].getParents().isEmpty()); //0 is later than 1!
QVERIFY(!commits[0].getParents().isEmpty()); //initial commit is on the top
QVERIFY(commits[1].getCommit().contains(QRegExp("^\\w{,40}$")));
QVERIFY(commits[0].getCommit().contains(QRegExp("^\\w{,40}$")));
QVERIFY(commits[0].getParents()[0].contains(QRegExp("^\\w{,40}$")));
}
void GitInitTest::testAnnotation()
{
repoInit();
addFiles();
commitFiles();
// called after commitFiles
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("An appended line"), QIODevice::Append));
VcsJob* j = m_plugin->commit(QStringLiteral("KDevelop's Test commit3"), QList() << QUrl::fromLocalFile(gitTest_BaseDir()));
VERIFYJOB(j);
j = m_plugin->annotate(QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName()), VcsRevision::createSpecialRevision(VcsRevision::Head));
VERIFYJOB(j);
QList results = j->fetchResults().toList();
QCOMPARE(results.size(), 2);
QVERIFY(results.at(0).canConvert());
VcsAnnotationLine annotation = results.at(0).value();
QCOMPARE(annotation.lineNumber(), 0);
QCOMPARE(annotation.commitMessage(), QStringLiteral("KDevelop's Test commit2"));
QVERIFY(results.at(1).canConvert());
annotation = results.at(1).value();
QCOMPARE(annotation.lineNumber(), 1);
QCOMPARE(annotation.commitMessage(), QStringLiteral("KDevelop's Test commit3"));
}
void GitInitTest::testRemoveEmptyFolder()
{
repoInit();
QDir d(gitTest_BaseDir());
d.mkdir(QStringLiteral("emptydir"));
VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"emptydir/"));
if (j) VERIFYJOB(j);
QVERIFY(!d.exists(QStringLiteral("emptydir")));
}
void GitInitTest::testRemoveEmptyFolderInFolder()
{
repoInit();
QDir d(gitTest_BaseDir());
d.mkdir(QStringLiteral("dir"));
QDir d2(gitTest_BaseDir()+"dir");
d2.mkdir(QStringLiteral("emptydir"));
VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"dir/"));
if (j) VERIFYJOB(j);
QVERIFY(!d.exists(QStringLiteral("dir")));
}
void GitInitTest::testRemoveUnindexedFile()
{
repoInit();
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("An appended line"), QIODevice::Append));
VcsJob* j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + gitTest_FileName()));
if (j) VERIFYJOB(j);
QVERIFY(!QFile::exists(gitTest_BaseDir() + gitTest_FileName()));
}
void GitInitTest::testRemoveFolderContainingUnversionedFiles()
{
repoInit();
QDir d(gitTest_BaseDir());
d.mkdir(QStringLiteral("dir"));
QVERIFY(writeFile(gitTest_BaseDir() + "dir/foo", QStringLiteral("An appended line"), QIODevice::Append));
VcsJob* j = m_plugin->add(QList() << QUrl::fromLocalFile(gitTest_BaseDir()+"dir"));
VERIFYJOB(j);
j = m_plugin->commit(QStringLiteral("initial commit"), QList() << QUrl::fromLocalFile(gitTest_BaseDir()));
VERIFYJOB(j);
QVERIFY(writeFile(gitTest_BaseDir() + "dir/bar", QStringLiteral("An appended line"), QIODevice::Append));
j = m_plugin->remove(QList() << QUrl::fromLocalFile(gitTest_BaseDir() + "dir"));
if (j) VERIFYJOB(j);
QVERIFY(!QFile::exists(gitTest_BaseDir() + "dir"));
}
void GitInitTest::removeTempDirs()
{
for (const auto& dirPath : {gitTest_BaseDir(), gitTest_BaseDir2()}) {
QDir dir(dirPath);
if (dir.exists() && !dir.removeRecursively()) {
qDebug() << "QDir::removeRecursively(" << dirPath << ") returned false";
}
}
}
void GitInitTest::testDiff()
{
repoInit();
addFiles();
commitFiles();
QVERIFY(writeFile(gitTest_BaseDir() + gitTest_FileName(), QStringLiteral("something else")));
VcsRevision srcrev = VcsRevision::createSpecialRevision(VcsRevision::Base);
VcsRevision dstrev = VcsRevision::createSpecialRevision(VcsRevision::Working);
VcsJob* j = m_plugin->diff(QUrl::fromLocalFile(gitTest_BaseDir()), srcrev, dstrev, VcsDiff::DiffUnified, IBasicVersionControl::Recursive);
VERIFYJOB(j);
KDevelop::VcsDiff d = j->fetchResults().value();
QVERIFY(d.baseDiff().isLocalFile());
QString path = d.baseDiff().toLocalFile();
QVERIFY(QDir().exists(path+"/.git"));
}
QTEST_MAIN(GitInitTest)
// #include "gittest.moc"
diff --git a/project/projectutils.h b/project/projectutils.h
index 1b44c43410..6876b2cdc3 100644
--- a/project/projectutils.h
+++ b/project/projectutils.h
@@ -1,62 +1,62 @@
/*
Copyright (C) 2011 David Nolden
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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef KDEVPLATFORM_PROJECTUTILS_H
#define KDEVPLATFORM_PROJECTUTILS_H
#include "projectexport.h"
-#include
+#include
#include
class QMenu;
namespace KDevelop {
class ProjectBaseItem;
class ProjectFileItem;
// TODO: Move to own header? Rename to ProjectItemContext and remove original from context.h?
class KDEVPLATFORMPROJECT_EXPORT ProjectItemContextImpl : public ProjectItemContext
{
public:
explicit ProjectItemContextImpl(const QList& items);
virtual QList urls() const override;
private:
Q_DISABLE_COPY(ProjectItemContextImpl)
};
/**
* Adds menu entries for all parent folders of the given item, each containing all the project
* items for the folder, at the end of the given menu.
* */
KDEVPLATFORMPROJECT_EXPORT void populateParentItemsMenu( ProjectBaseItem* item, QMenu* menu );
/**
* Returns all the files that have @p projectItem as ancestor
*/
KDEVPLATFORMPROJECT_EXPORT QList allFiles(ProjectBaseItem* projectItem);
}
#endif // KDEVPLATFORM_PROJECTUTILS_H
diff --git a/shell/filteredproblemstore.cpp b/shell/filteredproblemstore.cpp
index 0daabb9d9a..40962a6a62 100644
--- a/shell/filteredproblemstore.cpp
+++ b/shell/filteredproblemstore.cpp
@@ -1,310 +1,311 @@
/*
* Copyright 2015 Laszlo Kis-Adam
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "filteredproblemstore.h"
#include "problem.h"
#include "watcheddocumentset.h"
#include "problemstorenode.h"
#include
using namespace KDevelop;
namespace
{
/// Adds diagnostics as sub-nodes
void addDiagnostics(ProblemStoreNode *node, const QVector &diagnostics)
{
foreach (const IProblem::Ptr &ptr, diagnostics) {
ProblemNode *child = new ProblemNode(node, ptr);
node->addChild(child);
addDiagnostics(child, ptr->diagnostics());
}
}
/**
* @brief Base class for grouping strategy classes
*
* These classes build the problem tree based on the respective strategies
*/
class GroupingStrategy
{
public:
GroupingStrategy( ProblemStoreNode *root )
: m_rootNode(root)
, m_groupedRootNode(new ProblemStoreNode())
{
}
virtual ~GroupingStrategy(){
}
/// Add a problem to the appropriate group
virtual void addProblem(const IProblem::Ptr &problem) = 0;
/// Find the specified noe
const ProblemStoreNode* findNode(int row, ProblemStoreNode *parent = nullptr) const
{
if (parent == nullptr)
return m_groupedRootNode->child(row);
else
return parent->child(row);
}
/// Returns the number of children nodes
int count(ProblemStoreNode *parent = nullptr)
{
if (parent == nullptr)
return m_groupedRootNode->count();
else
return parent->count();
}
/// Clears the problems
virtual void clear()
{
m_groupedRootNode->clear();
}
protected:
ProblemStoreNode *m_rootNode;
QScopedPointer m_groupedRootNode;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Implements no grouping strategy, that is just stores the problems without any grouping
class NoGroupingStrategy final : public GroupingStrategy
{
public:
NoGroupingStrategy(ProblemStoreNode *root)
: GroupingStrategy(root)
{
}
void addProblem(const IProblem::Ptr &problem) override
{
ProblemNode *node = new ProblemNode(m_groupedRootNode.data(), problem);
addDiagnostics(node, problem->diagnostics());
m_groupedRootNode->addChild(node);
}
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Implements grouping based on path
class PathGroupingStrategy final : public GroupingStrategy
{
public:
PathGroupingStrategy(ProblemStoreNode *root)
: GroupingStrategy(root)
{
}
void addProblem(const IProblem::Ptr &problem) override
{
QString path = problem->finalLocation().document.str();
/// See if we already have this path
ProblemStoreNode *parent = nullptr;
foreach (ProblemStoreNode *node, m_groupedRootNode->children()) {
if (node->label() == path) {
parent = node;
break;
}
}
/// If not add it!
if (parent == nullptr) {
parent = new LabelNode(m_groupedRootNode.data(), path);
m_groupedRootNode->addChild(parent);
}
ProblemNode *node = new ProblemNode(parent, problem);
addDiagnostics(node, problem->diagnostics());
parent->addChild(node);
}
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Implements grouping based on severity
class SeverityGroupingStrategy final : public GroupingStrategy
{
public:
enum SeverityGroups
{
GroupError = 0,
GroupWarning = 1,
GroupHint = 2
};
SeverityGroupingStrategy(ProblemStoreNode *root)
: GroupingStrategy(root)
{
/// Create the groups on construction, so there's no need to search for them on addition
m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Error")));
m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Warning")));
m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Hint")));
}
void addProblem(const IProblem::Ptr &problem) override
{
ProblemStoreNode *parent = nullptr;
switch (problem->severity()) {
case IProblem::Error: parent = m_groupedRootNode->child(GroupError); break;
case IProblem::Warning: parent = m_groupedRootNode->child(GroupWarning); break;
case IProblem::Hint: parent = m_groupedRootNode->child(GroupHint); break;
+ default: break;
}
ProblemNode *node = new ProblemNode(m_groupedRootNode.data(), problem);
addDiagnostics(node, problem->diagnostics());
parent->addChild(node);
}
void clear() override
{
m_groupedRootNode->child(GroupError)->clear();
m_groupedRootNode->child(GroupWarning)->clear();
m_groupedRootNode->child(GroupHint)->clear();
}
};
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace KDevelop
{
struct FilteredProblemStorePrivate
{
FilteredProblemStorePrivate(FilteredProblemStore* q)
: q(q)
, m_strategy(new NoGroupingStrategy(q->rootNode()))
, m_grouping(NoGrouping)
{
}
/// Tells if the problem matches the filters
bool match(const IProblem::Ptr &problem) const;
FilteredProblemStore* q;
QScopedPointer m_strategy;
GroupingMethod m_grouping;
};
FilteredProblemStore::FilteredProblemStore(QObject *parent)
: ProblemStore(parent)
, d(new FilteredProblemStorePrivate(this))
{
}
FilteredProblemStore::~FilteredProblemStore()
{
}
void FilteredProblemStore::addProblem(const IProblem::Ptr &problem)
{
ProblemStore::addProblem(problem);
if (d->match(problem))
d->m_strategy->addProblem(problem);
}
const ProblemStoreNode* FilteredProblemStore::findNode(int row, ProblemStoreNode *parent) const
{
return d->m_strategy->findNode(row, parent);
}
int FilteredProblemStore::count(ProblemStoreNode *parent) const
{
return d->m_strategy->count(parent);
}
void FilteredProblemStore::clear()
{
d->m_strategy->clear();
ProblemStore::clear();
}
void FilteredProblemStore::rebuild()
{
emit beginRebuild();
d->m_strategy->clear();
foreach (ProblemStoreNode *node, rootNode()->children()) {
IProblem::Ptr problem = node->problem();
if (d->match(problem)) {
d->m_strategy->addProblem(problem);
}
}
emit endRebuild();
}
void FilteredProblemStore::setGrouping(int grouping)
{
GroupingMethod g = GroupingMethod(grouping);
if(g == d->m_grouping)
return;
d->m_grouping = g;
switch (g) {
case NoGrouping: d->m_strategy.reset(new NoGroupingStrategy(rootNode())); break;
case PathGrouping: d->m_strategy.reset(new PathGroupingStrategy(rootNode())); break;
case SeverityGrouping: d->m_strategy.reset(new SeverityGroupingStrategy(rootNode())); break;
}
rebuild();
emit changed();
}
int FilteredProblemStore::grouping() const
{
return d->m_grouping;
}
bool FilteredProblemStorePrivate::match(const IProblem::Ptr &problem) const
{
if(problem->severity()!=IProblem::NoSeverity)
{
/// If the problem severity isn't in the filter severities it's discarded
if(!q->severities().testFlag(problem->severity()))
return false;
}
else
{
if(!q->severities().testFlag(IProblem::Hint))//workaround for problems wothout correctly set severity
return false;
}
return true;
}
}
diff --git a/shell/problem.cpp b/shell/problem.cpp
index a5919d1941..e0a66ff8f8 100644
--- a/shell/problem.cpp
+++ b/shell/problem.cpp
@@ -1,174 +1,175 @@
/*
* Copyright 2015 Laszlo Kis-Adam
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include
#include
#include
struct DetectedProblemPrivate
{
DetectedProblemPrivate()
: m_severity(KDevelop::IProblem::Error)
, m_source(KDevelop::IProblem::Unknown)
{
}
QString m_description;
QString m_explanation;
KDevelop::IProblem::Severity m_severity;
KDevelop::IProblem::Source m_source;
KDevelop::DocumentRange m_range;
QVector m_diagnostics;
};
namespace KDevelop
{
DetectedProblem::DetectedProblem()
:d(new DetectedProblemPrivate())
{
}
DetectedProblem::~DetectedProblem()
{
clearDiagnostics();
}
IProblem::Source DetectedProblem::source() const
{
return d->m_source;
}
void DetectedProblem::setSource(Source source)
{
d->m_source = source;
}
QString DetectedProblem::sourceString() const
{
QString s;
switch(d->m_source)
{
case Unknown: s = i18n("Unknown"); break;
case Disk: s = i18n("Disk"); break;
case Preprocessor: s = i18n("Preprocessor"); break;
case Lexer: s = i18n("Lexer"); break;
case Parser: s = i18n("Parser"); break;
case DUChainBuilder: s = i18n("DuchainBuilder"); break;
case SemanticAnalysis: s = i18n("Semantic analysis"); break;
case ToDo: s = i18n("Todo"); break;
case Plugin: s = i18n("Plugin"); break;
}
return s;
}
DocumentRange DetectedProblem::finalLocation() const
{
return d->m_range;
}
void DetectedProblem::setFinalLocation(const DocumentRange &location)
{
d->m_range = location;
}
QString DetectedProblem::description() const
{
return d->m_description;
}
void DetectedProblem::setDescription(const QString &description)
{
d->m_description = description;
}
QString DetectedProblem::explanation() const
{
return d->m_explanation;
}
void DetectedProblem::setExplanation(const QString &explanation)
{
d->m_explanation = explanation;
}
IProblem::Severity DetectedProblem::severity() const
{
return d->m_severity;
}
void DetectedProblem::setSeverity(Severity severity)
{
d->m_severity = severity;
}
QString DetectedProblem::severityString() const
{
QString s;
switch(d->m_severity)
{
case Hint: s = i18n("Hint"); break;
case Warning: s = i18n("Warning"); break;
case Error: s = i18n("Error"); break;
+ default: break;
}
return s;
}
QVector DetectedProblem::diagnostics() const
{
return d->m_diagnostics;
}
void DetectedProblem::setDiagnostics(const QVector &diagnostics)
{
clearDiagnostics();
foreach (const Ptr &diagnostic, diagnostics) {
addDiagnostic(diagnostic);
}
}
void DetectedProblem::addDiagnostic(const Ptr &diagnostic)
{
DetectedProblem *dp = dynamic_cast(diagnostic.data());
Q_ASSERT(dp);
d->m_diagnostics.push_back(diagnostic);
}
void DetectedProblem::clearDiagnostics()
{
d->m_diagnostics.clear();
}
QExplicitlySharedDataPointer DetectedProblem::solutionAssistant() const
{
return {};
}
}
diff --git a/shell/problemmodel.cpp b/shell/problemmodel.cpp
index 06f34e299c..7e1ace1b6b 100644
--- a/shell/problemmodel.cpp
+++ b/shell/problemmodel.cpp
@@ -1,349 +1,351 @@
/*
* KDevelop Problem Reporter
*
* Copyright 2007 Hamish Rodda
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "problemmodel.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace {
QIcon iconForSeverity(KDevelop::IProblem::Severity severity)
{
switch (severity) {
case KDevelop::IProblem::Hint:
return QIcon::fromTheme(QStringLiteral("dialog-information"));
case KDevelop::IProblem::Warning:
return QIcon::fromTheme(QStringLiteral("dialog-warning"));
case KDevelop::IProblem::Error:
return QIcon::fromTheme(QStringLiteral("dialog-error"));
+ case KDevelop::IProblem::NoSeverity:
+ return {};
}
return {};
}
}
struct ProblemModelPrivate
{
ProblemModelPrivate(KDevelop::ProblemStore *store)
: m_problems(store)
, m_features(KDevelop::ProblemModel::NoFeatures)
{
}
QScopedPointer m_problems;
KDevelop::ProblemModel::Features m_features;
};
namespace KDevelop
{
ProblemModel::ProblemModel(QObject * parent, ProblemStore *store)
: QAbstractItemModel(parent)
, d(new ProblemModelPrivate(store))
{
if (!d->m_problems) {
d->m_problems.reset(new FilteredProblemStore());
d->m_features = ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter;
}
setScope(CurrentDocument);
connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemModel::setCurrentDocument);
connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProblemModel::closedDocument);
/// CompletionSettings include a list of todo markers we care for, so need to update
connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemModel::forceFullUpdate);
if (ICore::self()->documentController()->activeDocument()) {
setCurrentDocument(ICore::self()->documentController()->activeDocument());
}
connect(d->m_problems.data(), &ProblemStore::beginRebuild, this, &ProblemModel::onBeginRebuild);
connect(d->m_problems.data(), &ProblemStore::endRebuild, this, &ProblemModel::onEndRebuild);
}
ProblemModel::~ ProblemModel()
{
}
int ProblemModel::rowCount(const QModelIndex& parent) const
{
if (!parent.isValid()) {
return d->m_problems->count();
} else {
return d->m_problems->count(reinterpret_cast(parent.internalPointer()));
}
}
static QString displayUrl(const QUrl &url, const QUrl &base)
{
if (base.isParentOf(url)) {
return url.toDisplayString(QUrl::PreferLocalFile).mid(base.toDisplayString(QUrl::PreferLocalFile).length());
} else {
return ICore::self()->projectController()->prettyFileName(url, IProjectController::FormatPlain);
}
}
QVariant ProblemModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
QUrl baseDirectory = d->m_problems->currentDocument().toUrl().adjusted(QUrl::RemoveFilename);
IProblem::Ptr p = problemForIndex(index);
if (!p.constData()) {
if (role == Qt::DisplayRole && index.column() == Error) {
ProblemStoreNode *node = reinterpret_cast(index.internalPointer());
if (node) {
return node->label();
}
}
return {};
}
if (role == SeverityRole) {
return p->severity();
} else if (role == ProblemRole) {
return QVariant::fromValue(p);
}
switch (role) {
case Qt::DisplayRole:
switch (index.column()) {
case Source:
return p->sourceString();
case Error:
return p->description();
case File:
return displayUrl(p->finalLocation().document.toUrl(), baseDirectory);
case Line:
if (p->finalLocation().isValid()) {
return QString::number(p->finalLocation().start().line() + 1);
}
break;
case Column:
if (p->finalLocation().isValid()) {
return QString::number(p->finalLocation().start().column() + 1);
}
break;
}
break;
case Qt::DecorationRole:
if (index.column() == Error) {
return iconForSeverity(p->severity());
}
break;
case Qt::ToolTipRole:
return p->explanation();
default:
break;
}
return {};
}
QModelIndex ProblemModel::parent(const QModelIndex& index) const
{
ProblemStoreNode *node = reinterpret_cast(index.internalPointer());
if (!node) {
return {};
}
ProblemStoreNode *parent = node->parent();
if (!parent || parent->isRoot()) {
return {};
}
int idx = parent->index();
return createIndex(idx, 0, parent);
}
QModelIndex ProblemModel::index(int row, int column, const QModelIndex& parent) const
{
if (row < 0 || row >= rowCount(parent) || column < 0 || column >= LastColumn) {
return QModelIndex();
}
ProblemStoreNode *parentNode = reinterpret_cast(parent.internalPointer());
const ProblemStoreNode *node = d->m_problems->findNode(row, parentNode);
return createIndex(row, column, (void*)node);
}
int ProblemModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
return LastColumn;
}
IProblem::Ptr ProblemModel::problemForIndex(const QModelIndex& index) const
{
ProblemStoreNode *node = reinterpret_cast(index.internalPointer());
if (!node) {
return {};
} else {
return node->problem();
}
}
ProblemModel::Features ProblemModel::features() const
{
return d->m_features;
}
void ProblemModel::setFeatures(Features features)
{
d->m_features = features;
}
void ProblemModel::addProblem(const IProblem::Ptr &problem)
{
int c = d->m_problems->count();
beginInsertRows(QModelIndex(), c, c + 1);
d->m_problems->addProblem(problem);
endInsertRows();
}
void ProblemModel::setProblems(const QVector &problems)
{
beginResetModel();
d->m_problems->setProblems(problems);
endResetModel();
}
void ProblemModel::clearProblems()
{
beginResetModel();
d->m_problems->clear();
endResetModel();
}
QVariant ProblemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(orientation);
if (role != Qt::DisplayRole)
return {};
switch (section) {
case Source:
return i18nc("@title:column source of problem", "Source");
case Error:
return i18nc("@title:column problem description", "Problem");
case File:
return i18nc("@title:column file where problem was found", "File");
case Line:
return i18nc("@title:column line number with problem", "Line");
case Column:
return i18nc("@title:column column number with problem", "Column");
}
return {};
}
void ProblemModel::setCurrentDocument(IDocument* document)
{
Q_ASSERT(thread() == QThread::currentThread());
QUrl currentDocument = document->url();
/// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt
d->m_problems->setCurrentDocument(IndexedString(currentDocument));
}
void ProblemModel::closedDocument(IDocument* document)
{
if (IndexedString(document->url()) == d->m_problems->currentDocument())
{ // reset current document
d->m_problems->setCurrentDocument(IndexedString());
}
}
void ProblemModel::onBeginRebuild()
{
beginResetModel();
}
void ProblemModel::onEndRebuild()
{
endResetModel();
}
void ProblemModel::setScope(int scope)
{
Q_ASSERT(thread() == QThread::currentThread());
/// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt
d->m_problems->setScope(scope);
}
void ProblemModel::setSeverity(int severity)
{
switch (severity)
{
case KDevelop::IProblem::Error:
setSeverities(KDevelop::IProblem::Error);
break;
case KDevelop::IProblem::Warning:
setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning);
break;
case KDevelop::IProblem::Hint:
setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint);
break;
}
}
void ProblemModel::setSeverities(KDevelop::IProblem::Severities severities)
{
Q_ASSERT(thread() == QThread::currentThread());
/// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt
d->m_problems->setSeverities(severities);
}
void ProblemModel::setGrouping(int grouping)
{
/// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt
d->m_problems->setGrouping(grouping);
}
ProblemStore* ProblemModel::store() const
{
return d->m_problems.data();
}
}
diff --git a/shell/projectcontroller.cpp b/shell/projectcontroller.cpp
index b3a6895b7b..fe704c36f3 100644
--- a/shell/projectcontroller.cpp
+++ b/shell/projectcontroller.cpp
@@ -1,1196 +1,1195 @@
/* This file is part of KDevelop
Copyright 2006 Adam Treat
Copyright 2007 Andreas Pakulat
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "projectcontroller.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "core.h"
// TODO: Should get rid off this include (should depend on IProject only)
#include "project.h"
#include "mainwindow.h"
#include "shellextension.h"
#include "plugincontroller.h"
#include "configdialog.h"
#include "uicontroller.h"
#include "documentcontroller.h"
#include "openprojectdialog.h"
#include "sessioncontroller.h"
#include "session.h"
#include "debug.h"
namespace KDevelop
{
class ProjectControllerPrivate
{
public:
QList m_projects;
QMap< IProject*, QList > m_projectPlugins;
QPointer m_recentAction;
Core* m_core;
// IProject* m_currentProject;
ProjectModel* model;
QItemSelectionModel* selectionModel;
QPointer m_openProject;
QPointer m_fetchProject;
QPointer m_closeProject;
QPointer m_openConfig;
IProjectDialogProvider* dialog;
QList m_currentlyOpening; // project-file urls that are being opened
IProject* m_configuringProject;
ProjectController* q;
ProjectBuildSetModel* buildset;
bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file
bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller
QPointer m_changesModel;
QHash< IProject*, QPointer > m_parseJobs; // parse jobs that add files from the project to the background parser.
ProjectControllerPrivate( ProjectController* p )
: m_core(0), model(0), selectionModel(0), dialog(0), m_configuringProject(0), q(p), buildset(0), m_foundProjectFile(false), m_cleaningUp(false)
{
}
void unloadAllProjectPlugins()
{
if( m_projects.isEmpty() )
m_core->pluginControllerInternal()->unloadProjectPlugins();
}
void projectConfig( QObject * obj )
{
if( !obj )
return;
Project* proj = qobject_cast(obj);
if( !proj )
return;
QVector configPages;
auto mainWindow = m_core->uiController()->activeMainWindow();
ProjectConfigOptions options;
options.developerFile = proj->developerFile();
options.developerTempFile = proj->developerTempFile();
options.projectTempFile = proj->projectTempFile();
options.project = proj;
foreach (IPlugin* plugin, findPluginsForProject(proj)) {
for (int i = 0; i < plugin->perProjectConfigPages(); ++i) {
configPages.append(plugin->perProjectConfigPage(i, options, mainWindow));
}
}
Q_ASSERT(!m_configuringProject);
m_configuringProject = proj;
KDevelop::ConfigDialog cfgDlg(configPages, mainWindow);
QObject::connect(&cfgDlg, &ConfigDialog::configSaved, &cfgDlg, [this](ConfigPage* page) {
Q_UNUSED(page)
Q_ASSERT_X(m_configuringProject, Q_FUNC_INFO,
"ConfigDialog signalled project config change, but no project set for configuring!");
emit q->projectConfigurationChanged(m_configuringProject);
});
cfgDlg.setWindowTitle(i18n("Configure Project %1", proj->name()));
cfgDlg.exec();
proj->projectConfiguration()->sync();
m_configuringProject = nullptr;
}
void saveListOfOpenedProjects()
{
auto activeSession = Core::self()->activeSession();
if (!activeSession) {
return;
}
QList openProjects;
openProjects.reserve( m_projects.size() );
foreach( IProject* project, m_projects ) {
openProjects.append(project->projectFile().toUrl());
}
activeSession->setContainedProjects( openProjects );
}
// Recursively collects builder dependencies for a project.
static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project )
{
QList< IProjectBuilder* > auxBuilders = topBuilder->additionalBuilderPlugins( project );
destination.append( auxBuilders );
foreach( IProjectBuilder* auxBuilder, auxBuilders ) {
collectBuilders( destination, auxBuilder, project );
}
}
QVector findPluginsForProject( IProject* project )
{
QList plugins = m_core->pluginController()->loadedPlugins();
QVector projectPlugins;
QList buildersForKcm;
// Important to also include the "top" builder for the project, so
// projects with only one such builder are kept working. Otherwise the project config
// dialog is empty for such cases.
if( IBuildSystemManager* buildSystemManager = project->buildSystemManager() ) {
buildersForKcm << buildSystemManager->builder();
collectBuilders( buildersForKcm, buildSystemManager->builder(), project );
}
foreach(auto plugin, plugins) {
auto info = m_core->pluginController()->pluginInfo(plugin);
IProjectFileManager* manager = plugin->extension();
if( manager && manager != project->projectFileManager() )
{
// current plugin is a manager but does not apply to given project, skip
continue;
}
IProjectBuilder* builder = plugin->extension();
if ( builder && !buildersForKcm.contains( builder ) )
{
continue;
}
qCDebug(SHELL) << "Using plugin" << info.pluginId() << "for project" << project->name();
projectPlugins << plugin;
}
return projectPlugins;
}
void updateActionStates( Context* ctx )
{
ProjectItemContext* itemctx = dynamic_cast(ctx);
m_openConfig->setEnabled( itemctx && itemctx->items().count() == 1 );
m_closeProject->setEnabled( itemctx && itemctx->items().count() > 0 );
}
void openProjectConfig()
{
ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() );
if( ctx && ctx->items().count() == 1 )
{
q->configureProject( ctx->items().at(0)->project() );
}
}
void closeSelectedProjects()
{
ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() );
if( ctx && ctx->items().count() > 0 )
{
QSet projects;
foreach( ProjectBaseItem* item, ctx->items() )
{
projects.insert( item->project() );
}
foreach( IProject* project, projects )
{
q->closeProject( project );
}
}
}
void importProject(const QUrl& url_)
{
QUrl url(url_);
if (url.isLocalFile()) {
const QString path = QFileInfo(url.toLocalFile()).canonicalFilePath();
if (!path.isEmpty()) {
url = QUrl::fromLocalFile(path);
}
}
if ( !url.isValid() )
{
KMessageBox::error(Core::self()->uiControllerInternal()->activeMainWindow(),
i18n("Invalid Location: %1", url.toDisplayString(QUrl::PreferLocalFile)));
return;
}
if ( m_currentlyOpening.contains(url))
{
qCDebug(SHELL) << "Already opening " << url << ". Aborting.";
KPassivePopup::message( i18n( "Project already being opened"),
i18n( "Already opening %1, not opening again",
url.toDisplayString(QUrl::PreferLocalFile) ),
m_core->uiController()->activeMainWindow() );
return;
}
foreach( IProject* project, m_projects )
{
if ( url == project->projectFile().toUrl() )
{
if ( dialog->userWantsReopen() )
{ // close first, then open again by falling through
q->closeProject(project);
} else { // abort
return;
}
}
}
m_currentlyOpening += url;
m_core->pluginControllerInternal()->loadProjectPlugins();
Project* project = new Project();
QObject::connect(project, &Project::aboutToOpen,
q, &ProjectController::projectAboutToBeOpened);
if ( !project->open( Path(url) ) )
{
m_currentlyOpening.removeAll(url);
q->abortOpeningProject(project);
project->deleteLater();
}
}
void areaChanged(Sublime::Area* area) {
KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection();
ac->action(QStringLiteral("commit_current_project"))->setEnabled(area->objectName() == QLatin1String("code"));
ac->action(QStringLiteral("commit_current_project"))->setVisible(area->objectName() == QLatin1String("code"));
}
};
IProjectDialogProvider::IProjectDialogProvider()
{}
IProjectDialogProvider::~IProjectDialogProvider()
{}
ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* const p) : d(p)
{}
ProjectDialogProvider::~ProjectDialogProvider()
{}
bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& createdFrom, const QString& manager )
{
KSharedConfigPtr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig );
if (!cfg->isConfigWritable(true)) {
qCDebug(SHELL) << "can't write to configfile";
return false;
}
KConfigGroup grp = cfg->group( "Project" );
grp.writeEntry( "Name", name );
grp.writeEntry( "CreatedFrom", createdFrom );
grp.writeEntry( "Manager", manager );
cfg->sync();
return true;
}
bool writeProjectSettingsToConfigFile(const QUrl& projectFileUrl, OpenProjectDialog* dlg)
{
if ( !projectFileUrl.isLocalFile() ) {
QTemporaryFile tmp;
if ( !tmp.open() ) {
return false;
}
if ( !writeNewProjectFile( tmp.fileName(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ) ) {
return false;
}
// explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519
tmp.close();
auto uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmp.fileName()), projectFileUrl);
KJobWidgets::setWindow(uploadJob, Core::self()->uiControllerInternal()->defaultMainWindow());
return uploadJob->exec();
}
// Here and above we take .filename() part of the selectedUrl() to make it relative to the project root,
// thus keeping .kdev file relocatable
return writeNewProjectFile( projectFileUrl.toLocalFile(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() );
}
bool projectFileExists( const QUrl& u )
{
if( u.isLocalFile() )
{
return QFileInfo::exists( u.toLocalFile() );
} else
{
auto statJob = KIO::stat(u, KIO::StatJob::DestinationSide, 0);
KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->activeMainWindow());
return statJob->exec();
}
}
bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg )
{
KSharedConfigPtr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig );
KConfigGroup grp = cfg->group( "Project" );
QString defaultName = dlg->projectFileUrl().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).fileName();
return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) &&
grp.readEntry( "Manager", QString() ) == dlg->projectManager();
}
QUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const QUrl& startUrl)
{
Q_ASSERT(d);
OpenProjectDialog dlg( fetch, startUrl, Core::self()->uiController()->activeMainWindow() );
if(dlg.exec() == QDialog::Rejected)
return QUrl();
QUrl projectFileUrl = dlg.projectFileUrl();
qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg.projectName() << dlg.projectManager();
// controls if existing project file should be saved
bool writeProjectConfigToFile = true;
if( projectFileExists( projectFileUrl ) )
{
// check whether config is equal
bool shouldAsk = true;
if( projectFileUrl == dlg.selectedUrl() )
{
if( projectFileUrl.isLocalFile() )
{
shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), &dlg );
} else {
shouldAsk = false;
QTemporaryFile tmpFile;
if (tmpFile.open()) {
auto downloadJob = KIO::file_copy(projectFileUrl, QUrl::fromLocalFile(tmpFile.fileName()));
KJobWidgets::setWindow(downloadJob, qApp->activeWindow());
if (downloadJob->exec()) {
shouldAsk = !equalProjectFile(tmpFile.fileName(), &dlg);
}
}
}
}
if ( shouldAsk )
{
KGuiItem yes = KStandardGuiItem::yes();
yes.setText(i18n("Override"));
yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration."));
yes.setIcon(QIcon());
KGuiItem no = KStandardGuiItem::no();
no.setText(i18n("Open Existing File"));
no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration."));
no.setIcon(QIcon());
KGuiItem cancel = KStandardGuiItem::cancel();
cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project."));
int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(),
i18n("There already exists a project configuration file at %1.\n"
"Do you want to override it or open the existing file?", projectFileUrl.toDisplayString(QUrl::PreferLocalFile)),
i18n("Override existing project configuration"), yes, no, cancel );
if ( ret == KMessageBox::No )
{
writeProjectConfigToFile = false;
} else if ( ret == KMessageBox::Cancel )
{
return QUrl();
} // else fall through and write new file
} else {
writeProjectConfigToFile = false;
}
}
if (writeProjectConfigToFile) {
if (!writeProjectSettingsToConfigFile(projectFileUrl, &dlg)) {
KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(),
i18n("Unable to create configuration file %1", projectFileUrl.url()));
return QUrl();
}
}
return projectFileUrl;
}
bool ProjectDialogProvider::userWantsReopen()
{
Q_ASSERT(d);
return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(),
i18n( "Reopen the current project?" ) )
== KMessageBox::No) ? false : true;
}
void ProjectController::setDialogProvider(IProjectDialogProvider* dialog)
{
Q_ASSERT(d->dialog);
delete d->dialog;
d->dialog = dialog;
}
ProjectController::ProjectController( Core* core )
: IProjectController( core ), d( new ProjectControllerPrivate( this ) )
{
qRegisterMetaType>();
setObjectName(QStringLiteral("ProjectController"));
d->m_core = core;
d->model = new ProjectModel();
//NOTE: this is required to be called here, such that the
// actions are available when the UI controller gets
// initialized *before* the project controller
if (Core::self()->setupFlags() != Core::NoUi) {
setupActions();
}
}
void ProjectController::setupActions()
{
KActionCollection * ac =
d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection();
QAction*action;
d->m_openProject = action = ac->addAction( QStringLiteral("project_open") );
action->setText(i18nc( "@action", "Open / Import Project..." ) );
action->setToolTip( i18nc( "@info:tooltip", "Open or import project" ) );
action->setWhatsThis( i18nc( "@info:whatsthis", "Open an existing KDevelop 4 project or import "
"an existing Project into KDevelop 4. This entry "
"allows one to select a KDevelop4 project file "
"or an existing directory to open it in KDevelop. "
"When opening an existing directory that does "
"not yet have a KDevelop4 project file, the file "
"will be created." ) );
action->setIcon(QIcon::fromTheme(QStringLiteral("project-open")));
connect(action, &QAction::triggered, this, [&] { openProject(); });
d->m_fetchProject = action = ac->addAction( QStringLiteral("project_fetch") );
action->setText(i18nc( "@action", "Fetch Project..." ) );
action->setIcon( QIcon::fromTheme( QStringLiteral("edit-download") ) );
action->setToolTip( i18nc( "@info:tooltip", "Fetch project" ) );
action->setWhatsThis( i18nc( "@info:whatsthis", "Guides the user through the project fetch "
"and then imports it into KDevelop 4." ) );
// action->setIcon(QIcon::fromTheme("project-open"));
connect( action, &QAction::triggered, this, &ProjectController::fetchProject );
// action = ac->addAction( "project_close" );
// action->setText( i18n( "C&lose Project" ) );
// connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) );
// action->setToolTip( i18n( "Close project" ) );
// action->setWhatsThis( i18n( "Closes the current project." ) );
// action->setEnabled( false );
d->m_closeProject = action = ac->addAction( QStringLiteral("project_close") );
connect( action, &QAction::triggered, this, [&] { d->closeSelectedProjects(); } );
action->setText( i18nc( "@action", "Close Project(s)" ) );
action->setIcon( QIcon::fromTheme( QStringLiteral("project-development-close") ) );
action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) );
action->setEnabled( false );
d->m_openConfig = action = ac->addAction( QStringLiteral("project_open_config") );
connect( action, &QAction::triggered, this, [&] { d->openProjectConfig(); } );
action->setText( i18n( "Open Configuration..." ) );
action->setIcon( QIcon::fromTheme(QStringLiteral("configure")) );
action->setEnabled( false );
action = ac->addAction( QStringLiteral("commit_current_project") );
connect( action, &QAction::triggered, this, &ProjectController::commitCurrentProject );
action->setText( i18n( "Commit Current Project..." ) );
action->setIconText( i18n( "Commit..." ) );
action->setIcon( QIcon::fromTheme(QStringLiteral("svn-commit")) );
connect(d->m_core->uiControllerInternal()->defaultMainWindow(), &MainWindow::areaChanged,
this, [&] (Sublime::Area* area) { d->areaChanged(area); });
d->m_core->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(action);
KSharedConfig * config = KSharedConfig::openConfig().data();
// KConfigGroup group = config->group( "General Options" );
d->m_recentAction = KStandardAction::openRecent(this, SLOT(openProject(QUrl)), this);
ac->addAction( QStringLiteral("project_open_recent"), d->m_recentAction );
d->m_recentAction->setText( i18n( "Open Recent Project" ) );
d->m_recentAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) );
d->m_recentAction->loadEntries( KConfigGroup(config, "RecentProjects") );
QAction* openProjectForFileAction = new QAction( this );
ac->addAction(QStringLiteral("project_open_for_file"), openProjectForFileAction);
openProjectForFileAction->setText(i18n("Open Project for Current File"));
connect( openProjectForFileAction, &QAction::triggered, this, &ProjectController::openProjectForUrlSlot);
}
ProjectController::~ProjectController()
{
delete d->model;
delete d->dialog;
delete d;
}
void ProjectController::cleanup()
{
if ( d->m_currentlyOpening.isEmpty() ) {
d->saveListOfOpenedProjects();
}
d->m_cleaningUp = true;
if( buildSetModel() ) {
buildSetModel()->storeToSession( Core::self()->activeSession() );
}
closeAllProjects();
}
void ProjectController::initialize()
{
d->buildset = new ProjectBuildSetModel( this );
buildSetModel()->loadFromSession( Core::self()->activeSession() );
connect( this, &ProjectController::projectOpened,
d->buildset, &ProjectBuildSetModel::loadFromProject );
connect( this, &ProjectController::projectClosing,
d->buildset, &ProjectBuildSetModel::saveToProject );
connect( this, &ProjectController::projectClosed,
d->buildset, &ProjectBuildSetModel::projectClosed );
d->selectionModel = new QItemSelectionModel(d->model);
loadSettings(false);
d->dialog = new ProjectDialogProvider(d);
QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ProjectController"),
this, QDBusConnection::ExportScriptableSlots );
KSharedConfigPtr config = Core::self()->activeSession()->config();
KConfigGroup group = config->group( "General Options" );
QList openProjects = group.readEntry( "Open Projects", QList() );
QMetaObject::invokeMethod(this, "openProjects", Qt::QueuedConnection, Q_ARG(QList, openProjects));
connect( Core::self()->selectionController(), &ISelectionController::selectionChanged,
this, [&] (Context* ctx) { d->updateActionStates(ctx); } );
}
void ProjectController::openProjects(const QList& projects)
{
foreach (const QUrl& url, projects)
openProject(url);
}
void ProjectController::loadSettings( bool projectIsLoaded )
{
Q_UNUSED(projectIsLoaded)
}
void ProjectController::saveSettings( bool projectIsLoaded )
{
Q_UNUSED( projectIsLoaded );
}
int ProjectController::projectCount() const
{
return d->m_projects.count();
}
IProject* ProjectController::projectAt( int num ) const
{
if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() )
return d->m_projects.at( num );
return 0;
}
QList ProjectController::projects() const
{
return d->m_projects;
}
void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, KIO::UDSEntryList entries ) {
KIO::SimpleJob* job(dynamic_cast(_job));
Q_ASSERT(job);
foreach(const KIO::UDSEntry& entry, entries) {
if(d->m_foundProjectFile)
break;
if(!entry.isDir()) {
QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME );
if(name.endsWith(QLatin1String(".kdev4"))) {
//We have found a project-file, open it
openProject(Path(Path(job->url()), name).toUrl());
d->m_foundProjectFile = true;
}
}
}
}
void ProjectController::openProjectForUrlSlot(bool) {
if(ICore::self()->documentController()->activeDocument()) {
QUrl url = ICore::self()->documentController()->activeDocument()->url();
IProject* project = ICore::self()->projectController()->findProjectForUrl(url);
if(!project) {
openProjectForUrl(url);
}else{
KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("Project already open: %1", project->name()));
}
}else{
KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("No active document"));
}
}
void ProjectController::openProjectForUrl(const QUrl& sourceUrl) {
Q_ASSERT(!sourceUrl.isRelative());
QUrl dirUrl = sourceUrl.adjusted(QUrl::RemoveFilename);
QUrl testAt = dirUrl;
d->m_foundProjectFile = false;
while(!testAt.path().isEmpty()) {
- QUrl testProjectFile(testAt);
KIO::ListJob* job = KIO::listDir(testAt);
connect(job, &KIO::ListJob::entries, this, &ProjectController::eventuallyOpenProjectFile);
KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow());
job->exec();
if(d->m_foundProjectFile) {
//Fine! We have directly opened the project
d->m_foundProjectFile = false;
return;
}
QUrl oldTest = testAt.adjusted(QUrl::RemoveFilename);
if(oldTest == testAt)
break;
}
QUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl);
if(askForOpen.isValid())
openProject(askForOpen);
}
void ProjectController::openProject( const QUrl &projectFile )
{
QUrl url = projectFile;
if ( url.isEmpty() ) {
url = d->dialog->askProjectConfigLocation(false);
if ( url.isEmpty() ) {
return;
}
}
Q_ASSERT(!url.isRelative());
QList existingSessions;
if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url))
{
foreach( const Session* session, Core::self()->sessionController()->sessions())
{
if(session->containedProjects().contains(url))
{
existingSessions << session;
#if 0
///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc.
//If this session is empty, close it
if(Core::self()->sessionController()->activeSession()->description().isEmpty())
{
//Terminate this instance of kdevelop if the user agrees
foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows())
window->close();
}
#endif
}
}
}
if ( ! existingSessions.isEmpty() ) {
QDialog dialog(Core::self()->uiControllerInternal()->activeMainWindow());
dialog.setWindowTitle(i18n("Project Already Open"));
auto mainLayout = new QVBoxLayout(&dialog);
mainLayout->addWidget(new QLabel(i18n("The project you're trying to open is already open in at least one "
"other session.
What do you want to do?")));
QGroupBox sessions;
sessions.setLayout(new QVBoxLayout);
QRadioButton* newSession = new QRadioButton(i18n("Add project to current session"));
sessions.layout()->addWidget(newSession);
newSession->setChecked(true);
foreach ( const Session* session, existingSessions ) {
QRadioButton* button = new QRadioButton(i18n("Open session %1", session->description()));
button->setProperty("sessionid", session->id().toString());
sessions.layout()->addWidget(button);
}
sessions.layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
mainLayout->addWidget(&sessions);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Abort);
auto okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
mainLayout->addWidget(buttonBox);
bool success = dialog.exec();
if (!success)
return;
foreach ( const QObject* obj, sessions.children() ) {
if ( const QRadioButton* button = qobject_cast(obj) ) {
QString sessionid = button->property("sessionid").toString();
if ( button->isChecked() && ! sessionid.isEmpty() ) {
Core::self()->sessionController()->loadSession(sessionid);
return;
}
}
}
}
if ( url.isEmpty() )
{
url = d->dialog->askProjectConfigLocation(false);
}
if ( !url.isEmpty() )
{
d->importProject(url);
}
}
void ProjectController::fetchProject()
{
QUrl url = d->dialog->askProjectConfigLocation(true);
if ( !url.isEmpty() )
{
d->importProject(url);
}
}
void ProjectController::projectImportingFinished( IProject* project )
{
if( !project )
{
qWarning() << "OOOPS: 0-pointer project";
return;
}
IPlugin *managerPlugin = project->managerPlugin();
QList pluglist;
pluglist.append( managerPlugin );
d->m_projectPlugins.insert( project, pluglist );
d->m_projects.append( project );
d->saveListOfOpenedProjects();
if (Core::self()->setupFlags() != Core::NoUi)
{
d->m_recentAction->addUrl( project->projectFile().toUrl() );
KSharedConfig * config = KSharedConfig::openConfig().data();
KConfigGroup recentGroup = config->group("RecentProjects");
d->m_recentAction->saveEntries( recentGroup );
config->sync();
}
Q_ASSERT(d->m_currentlyOpening.contains(project->projectFile().toUrl()));
d->m_currentlyOpening.removeAll(project->projectFile().toUrl());
emit projectOpened( project );
reparseProject(project);
}
// helper method for closeProject()
void ProjectController::unloadUnusedProjectPlugins(IProject* proj)
{
QList pluginsForProj = d->m_projectPlugins.value( proj );
d->m_projectPlugins.remove( proj );
QList otherProjectPlugins;
Q_FOREACH( const QList& _list, d->m_projectPlugins )
{
otherProjectPlugins << _list;
}
QSet pluginsForProjSet = QSet::fromList( pluginsForProj );
QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins );
// loaded - target = tobe unloaded.
QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet );
Q_FOREACH( IPlugin* _plugin, tobeRemoved )
{
KPluginMetaData _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin );
if( _plugInfo.isValid() )
{
QString _plugName = _plugInfo.pluginId();
qCDebug(SHELL) << "about to unloading :" << _plugName;
Core::self()->pluginController()->unloadPlugin( _plugName );
}
}
}
// helper method for closeProject()
void ProjectController::closeAllOpenedFiles(IProject* proj)
{
foreach(IDocument* doc, Core::self()->documentController()->openDocuments()) {
if (proj->inProject(IndexedString(doc->url()))) {
doc->close();
}
}
}
// helper method for closeProject()
void ProjectController::initializePluginCleanup(IProject* proj)
{
// Unloading (and thus deleting) these plugins is not a good idea just yet
// as we're being called by the view part and it gets deleted when we unload the plugin(s)
// TODO: find a better place to unload
connect(proj, &IProject::destroyed, this, [&] { d->unloadAllProjectPlugins(); });
}
void ProjectController::takeProject(IProject* proj)
{
if (!proj) {
return;
}
// loading might have failed
d->m_currentlyOpening.removeAll(proj->projectFile().toUrl());
d->m_projects.removeAll(proj);
emit projectClosing(proj);
//Core::self()->saveSettings(); // The project file is being closed.
// Now we can save settings for all of the Core
// objects including this one!!
unloadUnusedProjectPlugins(proj);
closeAllOpenedFiles(proj);
proj->close();
if (d->m_projects.isEmpty())
{
initializePluginCleanup(proj);
}
if(!d->m_cleaningUp)
d->saveListOfOpenedProjects();
emit projectClosed(proj);
}
void ProjectController::closeProject(IProject* proj)
{
takeProject(proj);
proj->deleteLater(); // be safe when deleting
}
void ProjectController::closeAllProjects()
{
foreach (auto project, d->m_projects) {
closeProject(project);
}
}
void ProjectController::abortOpeningProject(IProject* proj)
{
d->m_currentlyOpening.removeAll(proj->projectFile().toUrl());
emit projectOpeningAborted(proj);
}
ProjectModel* ProjectController::projectModel()
{
return d->model;
}
IProject* ProjectController::findProjectForUrl( const QUrl& url ) const
{
if (d->m_projects.isEmpty()) {
return 0;
}
ProjectBaseItem* item = d->model->itemForPath(IndexedString(url));
if (item) {
return item->project();
}
return 0;
}
IProject* ProjectController::findProjectByName( const QString& name )
{
Q_FOREACH( IProject* proj, d->m_projects )
{
if( proj->name() == name )
{
return proj;
}
}
return 0;
}
void ProjectController::configureProject( IProject* project )
{
d->projectConfig( project );
}
void ProjectController::addProject(IProject* project)
{
Q_ASSERT(project);
if (d->m_projects.contains(project)) {
qWarning() << "Project already tracked by this project controller:" << project;
return;
}
// fake-emit signals so listeners are aware of a new project being added
emit projectAboutToBeOpened(project);
project->setParent(this);
d->m_projects.append(project);
emit projectOpened(project);
}
QItemSelectionModel* ProjectController::projectSelectionModel()
{
return d->selectionModel;
}
bool ProjectController::isProjectNameUsed( const QString& name ) const
{
foreach( IProject* p, projects() )
{
if( p->name() == name )
{
return true;
}
}
return false;
}
QUrl ProjectController::projectsBaseDirectory() const
{
KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" );
return group.readEntry( "Projects Base Directory", QUrl::fromLocalFile( QDir::homePath() + "/projects" ) );
}
QString ProjectController::prettyFilePath(const QUrl& url, FormattingOptions format) const
{
IProject* project = Core::self()->projectController()->findProjectForUrl(url);
if(!project)
{
// Find a project with the correct base directory at least
foreach(IProject* candidateProject, Core::self()->projectController()->projects())
{
if(candidateProject->path().toUrl().isParentOf(url))
{
project = candidateProject;
break;
}
}
}
Path parent = Path(url).parent();
QString prefixText;
if (project) {
if (format == FormatHtml) {
prefixText = "" + project->name() + "/";
} else {
prefixText = project->name() + ':';
}
QString relativePath = project->path().relativePath(parent);
if(relativePath.startsWith(QLatin1String("./"))) {
relativePath = relativePath.mid(2);
}
if (!relativePath.isEmpty()) {
prefixText += relativePath + '/';
}
} else {
prefixText = parent.pathOrUrl() + '/';
}
return prefixText;
}
QString ProjectController::prettyFileName(const QUrl& url, FormattingOptions format) const
{
IProject* project = Core::self()->projectController()->findProjectForUrl(url);
if(project && project->path() == Path(url))
{
if (format == FormatHtml) {
return "" + project->name() + "";
} else {
return project->name();
}
}
QString prefixText = prettyFilePath( url, format );
if (format == FormatHtml) {
return prefixText + "" + url.fileName() + "";
} else {
return prefixText + url.fileName();
}
}
ContextMenuExtension ProjectController::contextMenuExtension ( Context* ctx )
{
ContextMenuExtension ext;
if ( ctx->type() != Context::ProjectItemContext || !static_cast(ctx)->items().isEmpty() ) {
return ext;
}
ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject);
ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject);
ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentAction);
return ext;
}
ProjectBuildSetModel* ProjectController::buildSetModel()
{
return d->buildset;
}
ProjectChangesModel* ProjectController::changesModel()
{
if(!d->m_changesModel)
d->m_changesModel=new ProjectChangesModel(this);
return d->m_changesModel;
}
void ProjectController::commitCurrentProject()
{
IDocument* doc=ICore::self()->documentController()->activeDocument();
if(!doc)
return;
QUrl url=doc->url();
IProject* project = ICore::self()->projectController()->findProjectForUrl(url);
if(project && project->versionControlPlugin()) {
IPlugin* plugin = project->versionControlPlugin();
IBasicVersionControl* vcs=plugin->extension();
if(vcs) {
ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent);
const Path basePath = project->path();
VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, basePath.toUrl()));
bool ret = showVcsDiff(patchSource);
if(!ret) {
VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource);
commitDialog->setCommitCandidates(patchSource->infos());
commitDialog->exec();
}
}
}
}
QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const
{
Path path(path_);
IProject* sourceDirProject = 0, *buildDirProject = 0;
Q_FOREACH(IProject* proj, d->m_projects)
{
if(proj->path().isParentOf(path) || proj->path() == path)
sourceDirProject = proj;
if(proj->buildSystemManager())
{
Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem());
if(buildDir.isValid() && (buildDir.isParentOf(path) || buildDir == path))
buildDirProject = proj;
}
}
if(!reverse)
{
// Map-target is the build directory
if(sourceDirProject && sourceDirProject->buildSystemManager())
{
// We're in the source, map into the build directory
QString relativePath = sourceDirProject->path().relativePath(path);
Path build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem());
build.addPath(relativePath);
while(!QFile::exists(build.path()))
build = build.parent();
return build.pathOrUrl();
}else if(buildDirProject && fallbackRoot)
{
// We're in the build directory, map to the build directory root
return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl();
}
}else{
// Map-target is the source directory
if(buildDirProject)
{
Path build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem());
// We're in the source, map into the build directory
QString relativePath = build.relativePath(path);
Path source = buildDirProject->path();
source.addPath(relativePath);
while(!QFile::exists(source.path()))
source = source.parent();
return source.pathOrUrl();
}else if(sourceDirProject && fallbackRoot)
{
// We're in the source directory, map to the root
return sourceDirProject->path().pathOrUrl();
}
}
return QString();
}
void ProjectController::reparseProject( IProject* project, bool forceUpdate )
{
if (auto job = d->m_parseJobs.value(project)) {
job->kill();
}
d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate);
ICore::self()->runController()->registerJob(d->m_parseJobs[project]);
}
}
#include "moc_projectcontroller.cpp"
diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp
index cf3ab18b61..bdb2667dee 100644
--- a/shell/runcontroller.cpp
+++ b/shell/runcontroller.cpp
@@ -1,1058 +1,1057 @@
/* This file is part of KDevelop
Copyright 2007-2008 Hamish Rodda
Copyright 2008 Aleix Pol
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "runcontroller.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "core.h"
#include "plugincontroller.h"
#include "uicontroller.h"
#include "projectcontroller.h"
#include "mainwindow.h"
#include "launchconfiguration.h"
#include "launchconfigurationdialog.h"
#include "unitylauncher.h"
#include "debug.h"
#include
#include
#include
#include
using namespace KDevelop;
namespace {
namespace Strings {
QString LaunchConfigurationsGroup()
{
return QStringLiteral("Launch");
}
QString LaunchConfigurationsListEntry()
{
return QStringLiteral("Launch Configurations");
}
QString CurrentLaunchConfigProjectEntry()
{
return QStringLiteral("Current Launch Config Project");
}
QString CurrentLaunchConfigNameEntry()
{
return QStringLiteral("Current Launch Config GroupName");
}
QString ConfiguredFromProjectItemEntry()
{
return QStringLiteral("Configured from ProjectItem");
}
}
}
typedef QPair Target;
Q_DECLARE_METATYPE(Target)
//TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs
//TODO: Doesn't auto-select launch configs opened from projects
class DebugMode : public ILaunchMode
{
public:
DebugMode() {}
QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("tools-report-bug")); }
QString id() const override { return QStringLiteral("debug"); }
QString name() const override { return i18n("Debug"); }
};
class ProfileMode : public ILaunchMode
{
public:
ProfileMode() {}
QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); }
QString id() const override { return QStringLiteral("profile"); }
QString name() const override { return i18n("Profile"); }
};
class ExecuteMode : public ILaunchMode
{
public:
ExecuteMode() {}
QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); }
QString id() const override { return QStringLiteral("execute"); }
QString name() const override { return i18n("Execute"); }
};
class RunController::RunControllerPrivate
{
public:
QItemDelegate* delegate;
IRunController::State state;
RunController* q;
QHash jobs;
QAction* stopAction;
KActionMenu* stopJobsMenu;
QAction* runAction;
QAction* dbgAction;
KSelectAction* currentTargetAction;
QMap launchConfigurationTypes;
QList launchConfigurations;
QMap launchModes;
QSignalMapper* launchChangeMapper;
QSignalMapper* launchAsMapper;
QMap > launchAsInfo;
KDevelop::ProjectBaseItem* contextItem;
DebugMode* debugMode;
ExecuteMode* executeMode;
ProfileMode* profileMode;
UnityLauncher* unityLauncher;
bool hasLaunchConfigType( const QString& typeId )
{
return launchConfigurationTypes.contains( typeId );
}
void saveCurrentLaunchAction()
{
if (!currentTargetAction) return;
if( currentTargetAction->currentAction() )
{
KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() );
grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QLatin1String("") );
grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() );
grp.sync();
}
}
void configureLaunches()
{
LaunchConfigurationDialog dlg;
dlg.exec();
}
QString launchActionText( LaunchConfiguration* l )
{
QString label;
if( l->project() )
{
label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name());
} else
{
label = l->name();
}
return label;
}
void launchAs( int id )
{
//qCDebug(SHELL) << "Launching id:" << id;
QPair info = launchAsInfo[id];
//qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second;
LaunchConfigurationType* type = launchConfigurationTypeForId( info.first );
ILaunchMode* mode = q->launchModeForId( info.second );
//qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id();
if( type && mode )
{
ILauncher* launcher = 0;
foreach (ILauncher *l, type->launchers())
{
//qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes();
if (l->supportedModes().contains(mode->id())) {
launcher = l;
break;
}
}
if (launcher)
{
QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index());
ILaunchConfiguration* ilaunch = 0;
foreach (LaunchConfiguration *l, launchConfigurations) {
QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList());
if (l->type() == type && path == itemPath) {
qCDebug(SHELL) << "already generated ilaunch" << path;
ilaunch = l;
break;
}
}
if (!ilaunch) {
ilaunch = q->createLaunchConfiguration( type,
qMakePair( mode->id(), launcher->id() ),
contextItem->project(),
contextItem->text() );
LaunchConfiguration* launch = dynamic_cast( ilaunch );
type->configureLaunchFromItem( launch->config(), contextItem );
launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath);
//qCDebug(SHELL) << "created config, launching";
} else {
//qCDebug(SHELL) << "reusing generated config, launching";
}
q->setDefaultLaunch(ilaunch);
q->execute( mode->id(), ilaunch );
}
}
}
void updateCurrentLaunchAction()
{
if (!currentTargetAction) return;
KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" );
QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" );
LaunchConfiguration* l = 0;
if( currentTargetAction->currentAction() )
{
l = static_cast( currentTargetAction->currentAction()->data().value() );
} else if( !launchConfigurations.isEmpty() )
{
l = launchConfigurations.at( 0 );
}
if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) )
{
foreach( QAction* a, currentTargetAction->actions() )
{
LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) );
if( currentLaunchName == l->configGroupName()
&& ( ( currentLaunchProject.isEmpty() && !l->project() )
|| ( l->project() && l->project()->name() == currentLaunchProject ) ) )
{
a->setChecked( true );
break;
}
}
}
if( !currentTargetAction->currentAction() )
{
qCDebug(SHELL) << "oops no current action, using first if list is non-empty";
if( !currentTargetAction->actions().isEmpty() )
{
currentTargetAction->actions().at(0)->setChecked( true );
}
}
}
void addLaunchAction( LaunchConfiguration* l )
{
if (!currentTargetAction) return;
QAction* action = currentTargetAction->addAction(launchActionText( l ));
action->setData(qVariantFromValue(l));
}
void readLaunchConfigs( KSharedConfigPtr cfg, IProject* prj )
{
KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup());
QStringList configs = group.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() );
foreach( const QString& cfg, configs )
{
KConfigGroup grp = group.group( cfg );
if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) )
{
q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) );
}
}
}
LaunchConfigurationType* launchConfigurationTypeForId( const QString& id )
{
QMap::iterator it = launchConfigurationTypes.find( id );
if( it != launchConfigurationTypes.end() )
{
return it.value();
} else
{
qWarning() << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys();
}
return 0;
}
};
RunController::RunController(QObject *parent)
: IRunController(parent)
, d(new RunControllerPrivate)
{
setObjectName(QStringLiteral("RunController"));
QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"),
this, QDBusConnection::ExportScriptableSlots);
// TODO: need to implement compile only if needed before execute
// TODO: need to implement abort all running programs when project closed
d->currentTargetAction = 0;
d->state = Idle;
d->q = this;
d->delegate = new RunDelegate(this);
d->launchChangeMapper = new QSignalMapper( this );
d->launchAsMapper = 0;
d->contextItem = 0;
d->executeMode = 0;
d->debugMode = 0;
d->profileMode = 0;
d->unityLauncher = new UnityLauncher(this);
// desktopFileName() reports itself as "org.kdevelop.kdevelop" which is rubbish
d->unityLauncher->setLauncherId(KAboutData::applicationData().componentName() + QLatin1String(".desktop"));
if(!(Core::self()->setupFlags() & Core::NoUi)) {
// Note that things like registerJob() do not work without the actions, it'll simply crash.
setupActions();
}
}
RunController::~RunController()
{
delete d;
}
void KDevelop::RunController::launchChanged( LaunchConfiguration* l )
{
foreach( QAction* a, d->currentTargetAction->actions() )
{
if( static_cast( a->data().value() ) == l )
{
a->setText( d->launchActionText( l ) );
break;
}
}
}
void RunController::cleanup()
{
delete d->executeMode;
d->executeMode = 0;
delete d->profileMode;
d->profileMode = 0;
delete d->debugMode;
d->debugMode = 0;
stopAllProcesses();
d->saveCurrentLaunchAction();
}
void RunController::initialize()
{
d->executeMode = new ExecuteMode();
addLaunchMode( d->executeMode );
d->profileMode = new ProfileMode();
addLaunchMode( d->profileMode );
d->debugMode = new DebugMode;
addLaunchMode( d->debugMode );
d->readLaunchConfigs( Core::self()->activeSession()->config(), 0 );
foreach (IProject* project, Core::self()->projectController()->projects()) {
slotProjectOpened(project);
}
connect(Core::self()->projectController(), &IProjectController::projectOpened,
this, &RunController::slotProjectOpened);
connect(Core::self()->projectController(), &IProjectController::projectClosing,
this, &RunController::slotProjectClosing);
connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged,
this, &RunController::slotRefreshProject);
if( (Core::self()->setupFlags() & Core::NoUi) == 0 )
{
// Only do this in GUI mode
d->updateCurrentLaunchAction();
}
}
KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch)
{
if( !launch )
{
qCDebug(SHELL) << "execute called without launch config!";
return 0;
}
LaunchConfiguration *run = dynamic_cast(launch);
//TODO: Port to launch framework, probably needs to be part of the launcher
//if(!run.dependencies().isEmpty())
// ICore::self()->documentController()->saveAllDocuments(IDocument::Silent);
//foreach(KJob* job, run.dependencies())
//{
// jobs.append(job);
//}
qCDebug(SHELL) << "mode:" << runMode;
QString launcherId = run->launcherForMode( runMode );
qCDebug(SHELL) << "launcher id:" << launcherId;
ILauncher* launcher = run->type()->launcherForId( launcherId );
if( !launcher ) {
KMessageBox::error(
qApp->activeWindow(),
i18n("The current launch configuration does not support the '%1' mode.", runMode),
QLatin1String(""));
return 0;
}
KJob* launchJob = launcher->start(runMode, run);
registerJob(launchJob);
return launchJob;
}
void RunController::setupActions()
{
QAction* action;
// TODO not multi-window friendly, FIXME
KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection();
action = new QAction(i18n("Configure Launches..."), this);
ac->addAction(QStringLiteral("configure_launches"), action);
action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item
action->setStatusTip(i18n("Open Launch Configuration Dialog"));
action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog"));
action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones."));
connect(action, &QAction::triggered, this, [&] { d->configureLaunches(); });
d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Execute Launch"), this);
d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") );
ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9);
d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch"));
d->runAction->setStatusTip(i18n("Execute current launch"));
d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration."));
ac->addAction(QStringLiteral("run_execute"), d->runAction);
connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute);
d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug Launch"), this);
ac->setDefaultShortcut( d->dbgAction, Qt::Key_F9);
d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") );
d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch"));
d->dbgAction->setStatusTip(i18n("Debug current launch"));
d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger."));
ac->addAction(QStringLiteral("run_debug"), d->dbgAction);
connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug);
Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction);
// TODO: at least get a profile target, it's sad to have the menu entry without a profiler
// QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this);
// profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch"));
// profileAction->setStatusTip(i18n("Profile current launch"));
// profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler."));
// ac->addAction("run_profile", profileAction);
// connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile()));
action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop All Jobs"), this);
action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All"));
// Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut
ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape")));
action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs"));
action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped."));
action->setEnabled(false);
ac->addAction(QStringLiteral("run_stop_all"), action);
connect(action, &QAction::triggered, this, &RunController::stopAllProcesses);
Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action);
action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop"), this);
action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop"));
action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs"));
action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually."));
action->setEnabled(false);
ac->addAction(QStringLiteral("run_stop_menu"), action);
d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this);
d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration"));
d->currentTargetAction->setStatusTip(i18n("Current launch Configuration"));
d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked."));
ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction);
}
LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id )
{
return d->launchConfigurationTypeForId( id );
}
void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project)
{
d->readLaunchConfigs( project->projectConfiguration(), project );
d->updateCurrentLaunchAction();
}
void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project)
{
if (!d->currentTargetAction) return;
foreach (QAction* action, d->currentTargetAction->actions()) {
LaunchConfiguration* l = static_cast(qvariant_cast(action->data()));
if ( project == l->project() ) {
l->save();
d->launchConfigurations.removeAll(l);
delete l;
bool wasSelected = action->isChecked();
delete action;
if (wasSelected && !d->currentTargetAction->actions().isEmpty())
d->currentTargetAction->actions().at(0)->setChecked(true);
}
}
}
void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project)
{
slotProjectClosing(project);
slotProjectOpened(project);
}
void RunController::slotDebug()
{
if(d->launchConfigurations.isEmpty()) {
LaunchConfigurationDialog d;
d.exec();
}
if(!d->launchConfigurations.isEmpty())
executeDefaultLaunch( QStringLiteral("debug") );
}
void RunController::slotProfile()
{
if(d->launchConfigurations.isEmpty()) {
LaunchConfigurationDialog d;
d.exec();
}
if(!d->launchConfigurations.isEmpty())
executeDefaultLaunch( QStringLiteral("profile") );
}
void RunController::slotExecute()
{
if(d->launchConfigurations.isEmpty()) {
LaunchConfigurationDialog d;
d.exec();
}
if(!d->launchConfigurations.isEmpty())
executeDefaultLaunch( QStringLiteral("execute") );
}
LaunchConfiguration* KDevelop::RunController::defaultLaunch() const
{
QAction* projectAction = d->currentTargetAction->currentAction();
if( projectAction )
return static_cast(qvariant_cast(projectAction->data()));
return 0;
}
void KDevelop::RunController::registerJob(KJob * job)
{
if (!job)
return;
if (!(job->capabilities() & KJob::Killable)) {
// see e.g. https://bugs.kde.org/show_bug.cgi?id=314187
qWarning() << "non-killable job" << job << "registered - this might lead to crashes on shutdown.";
}
if (!d->jobs.contains(job)) {
QAction* stopJobAction = 0;
if (Core::self()->setupFlags() != Core::NoUi) {
stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", job->staticMetaObject.className()) : job->objectName(), this);
stopJobAction->setData(QVariant::fromValue(static_cast(job)));
d->stopJobsMenu->addAction(stopJobAction);
connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob);
job->setUiDelegate( new KDialogJobUiDelegate() );
}
d->jobs.insert(job, stopJobAction);
connect( job, &KJob::finished, this, &RunController::finished );
connect( job, &KJob::destroyed, this, &RunController::jobDestroyed );
// FIXME percent is a private signal and thus we cannot use new connext syntax
connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(jobPercentChanged()));
IRunController::registerJob(job);
emit jobRegistered(job);
}
job->start();
checkState();
}
void KDevelop::RunController::unregisterJob(KJob * job)
{
IRunController::unregisterJob(job);
Q_ASSERT(d->jobs.contains(job));
// Delete the stop job action
QAction *action = d->jobs.take(job);
if (action)
action->deleteLater();
checkState();
emit jobUnregistered(job);
}
void KDevelop::RunController::checkState()
{
bool running = false;
int jobCount = 0;
int totalProgress = 0;
for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) {
KJob *job = it.key();
if (!job->isSuspended()) {
running = true;
++jobCount;
totalProgress += job->percent();
}
}
d->unityLauncher->setProgressVisible(running);
if (jobCount > 0) {
d->unityLauncher->setProgress((totalProgress + 1) / jobCount);
} else {
d->unityLauncher->setProgress(0);
}
if ( ( d->state != Running ? false : true ) == running ) {
d->state = running ? Running : Idle;
emit runStateChanged(d->state);
}
if (Core::self()->setupFlags() != Core::NoUi) {
d->stopAction->setEnabled(running);
d->stopJobsMenu->setEnabled(running);
}
}
void KDevelop::RunController::stopAllProcesses()
{
// composite jobs might remove child jobs, see also:
// https://bugs.kde.org/show_bug.cgi?id=258904
// foreach already iterates over a copy
foreach (KJob* job, d->jobs.keys()) {
// now we check the real list whether it was deleted
if (!d->jobs.contains(job))
continue;
if (job->capabilities() & KJob::Killable) {
job->kill(KJob::EmitResult);
} else {
qWarning() << "cannot stop non-killable job: " << job;
}
}
}
void KDevelop::RunController::slotKillJob()
{
QAction* action = dynamic_cast(sender());
Q_ASSERT(action);
KJob* job = static_cast(qvariant_cast(action->data()));
if (job->capabilities() & KJob::Killable)
job->kill();
}
void KDevelop::RunController::finished(KJob * job)
{
unregisterJob(job);
switch (job->error()) {
case KJob::NoError:
case KJob::KilledJobError:
case OutputJob::FailedShownError:
break;
default:
{
///WARNING: do *not* use a nested event loop here, it might cause
/// random crashes later on, see e.g.:
/// https://bugs.kde.org/show_bug.cgi?id=309811
auto dialog = new QDialog(qApp->activeWindow());
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(i18n("Process Error"));
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dialog);
KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Warning,
job->errorString(), QStringList(),
QString(), 0, KMessageBox::NoExec);
dialog->show();
}
}
}
void RunController::jobDestroyed(QObject* job)
{
KJob* kjob = static_cast(job);
if (d->jobs.contains(kjob)) {
qWarning() << "job destroyed without emitting finished signal!";
unregisterJob(kjob);
}
}
void RunController::jobPercentChanged()
{
checkState();
}
void KDevelop::RunController::suspended(KJob * job)
{
Q_UNUSED(job);
checkState();
}
void KDevelop::RunController::resumed(KJob * job)
{
Q_UNUSED(job);
checkState();
}
QList< KJob * > KDevelop::RunController::currentJobs() const
{
return d->jobs.keys();
}
QList RunController::launchConfigurations() const
{
QList configs;
foreach (LaunchConfiguration *config, launchConfigurationsInternal())
configs << config;
return configs;
}
QList RunController::launchConfigurationsInternal() const
{
return d->launchConfigurations;
}
QList RunController::launchConfigurationTypes() const
{
return d->launchConfigurationTypes.values();
}
void RunController::addConfigurationType( LaunchConfigurationType* type )
{
if( !d->launchConfigurationTypes.contains( type->id() ) )
{
d->launchConfigurationTypes.insert( type->id(), type );
}
}
void RunController::removeConfigurationType( LaunchConfigurationType* type )
{
foreach( LaunchConfiguration* l, d->launchConfigurations )
{
if( l->type() == type )
{
removeLaunchConfigurationInternal( l );
}
}
d->launchConfigurationTypes.remove( type->id() );
}
void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode)
{
if( !d->launchModes.contains( mode->id() ) )
{
d->launchModes.insert( mode->id(), mode );
}
}
QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const
{
return d->launchModes.values();
}
void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode)
{
d->launchModes.remove( mode->id() );
}
KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const
{
QMap::iterator it = d->launchModes.find( id );
if( it != d->launchModes.end() )
{
return it.value();
}
return 0;
}
void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l)
{
if( !d->launchConfigurations.contains( l ) )
{
d->addLaunchAction( l );
d->launchConfigurations << l;
if( !d->currentTargetAction->currentAction() )
{
if( !d->currentTargetAction->actions().isEmpty() )
{
d->currentTargetAction->actions().at(0)->setChecked( true );
}
}
connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged );
}
}
void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l)
{
KConfigGroup launcherGroup;
if( l->project() ) {
launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() );
} else {
launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
}
QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() );
configs.removeAll( l->configGroupName() );
launcherGroup.deleteGroup( l->configGroupName() );
launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs );
launcherGroup.sync();
removeLaunchConfigurationInternal( l );
}
void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l)
{
foreach( QAction* a, d->currentTargetAction->actions() ) {
if( static_cast( a->data().value