diff --git a/projectmanagers/cmake/cmakemanager.cpp b/projectmanagers/cmake/cmakemanager.cpp index 937c8b6222..392df37977 100644 --- a/projectmanagers/cmake/cmakemanager.cpp +++ b/projectmanagers/cmake/cmakemanager.cpp @@ -1,917 +1,948 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2013 Aleix Pol * * 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. */ #include "cmakemanager.h" #include "cmakeedit.h" #include "cmakeutils.h" #include "cmakeprojectdata.h" #include "duchain/cmakeparsejob.h" #include "cmakeimportjsonjob.h" #include "debug.h" #include "settings/cmakepreferences.h" #include #include "cmakecodecompletionmodel.h" #include "cmakenavigationwidget.h" #include "icmakedocumentation.h" #include "cmakemodelitems.h" #include "testing/ctestutils.h" #include "cmakeserverimportjob.h" #include "cmakeserver.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 Q_DECLARE_METATYPE(KDevelop::IProject*); using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin(); ) const QString DIALOG_CAPTION = i18n("KDevelop - CMake Support"); CMakeManager::CMakeManager( QObject* parent, const QVariantList& ) : KDevelop::AbstractFileManagerPlugin( "kdevcmakemanager", parent ) , m_filter( new ProjectFilterManager( this ) ) { if (CMake::findExecutable().isEmpty()) { setErrorDescription(i18n("Unable to find a CMake executable. Is one installed on the system?")); m_highlight = nullptr; return; } m_highlight = new KDevelop::CodeHighlighting(this); new CodeCompletion(this, new CMakeCodeCompletionModel(this), name()); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing); connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded); // m_fileSystemChangeTimer = new QTimer(this); // m_fileSystemChangeTimer->setSingleShot(true); // m_fileSystemChangeTimer->setInterval(100); // connect(m_fileSystemChangeTimer,SIGNAL(timeout()),SLOT(filesystemBuffererTimeout())); } CMakeManager::~CMakeManager() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); } bool CMakeManager::hasBuildInfo(ProjectBaseItem* item) const { return m_projects[item->project()].compilationData.files.contains(item->path()); } Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const { // CMakeFolderItem *fi=dynamic_cast(item); // Path ret; // ProjectBaseItem* parent = fi ? fi->formerParent() : item->parent(); // if (parent) // ret=buildDirectory(parent); // else // ret=Path(CMake::currentBuildDir(item->project())); // // if(fi) // ret.addPath(fi->buildDir()); // return ret; return Path(CMake::currentBuildDir(item->project())); } KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project ) { CMake::checkForNeedingConfigure(project); return AbstractFileManagerPlugin::import(project); } class ChooseCMakeInterfaceJob : public ExecuteCompositeJob { Q_OBJECT public: ChooseCMakeInterfaceJob(IProject* project, CMakeManager* manager) : ExecuteCompositeJob(manager, {}) , project(project) , manager(manager) { } void start() override { server = new CMakeServer(project); connect(server, &CMakeServer::connected, this, &ChooseCMakeInterfaceJob::successfulConnection); connect(server, &CMakeServer::finished, this, &ChooseCMakeInterfaceJob::failedConnection); } private: void successfulConnection() { auto job = new CMakeServerImportJob(project, server, this); connect(job, &CMakeServerImportJob::result, this, [this, job](){ if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } void failedConnection(int code) { Q_ASSERT(code > 0); Q_ASSERT(!server->isServerAvailable()); server->deleteLater(); server = nullptr; // parse the JSON file CMakeImportJsonJob* job = new CMakeImportJsonJob(project, this); // create the JSON file if it doesn't exist auto commandsFile = CMake::commandsFile(project); if (!QFileInfo::exists(commandsFile.toLocalFile())) { qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure"; addSubjob(manager->builder()->configure(project)); } connect(job, &CMakeImportJsonJob::result, this, [this, job](){ if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } CMakeServer* server = nullptr; IProject* const project; CMakeManager* const manager; }; KJob* CMakeManager::createImportJob(ProjectFolderItem* item) { auto project = item->project(); auto job = new ChooseCMakeInterfaceJob(project, this); connect(job, &CMakeImportJsonJob::result, this, [this, job, project](){ if (job->error() != 0) { qCWarning(CMAKE) << "couldn't load json successfully" << project->name(); m_projects.remove(project); } }); const QList jobs = { job, KDevelop::AbstractFileManagerPlugin::createImportJob(item) // generate the file system listing }; Q_ASSERT(!jobs.contains(nullptr)); ExecuteCompositeJob* composite = new ExecuteCompositeJob(this, jobs); // even if the cmake call failed, we want to load the project so that the project can be worked on composite->setAbortOnError(false); return composite; } // QList CMakeManager::parse(ProjectFolderItem*) // { return QList< ProjectFolderItem* >(); } // // QList CMakeManager::targets() const { QList ret; foreach(IProject* p, m_projects.keys()) { ret+=p->projectItem()->targetList(); } return ret; } CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const { const auto & data = m_projects[item->project()].compilationData; QHash::const_iterator it = data.files.constFind(item->path()); if (it == data.files.constEnd()) { // if the item path contains a symlink, then we will not find it in the lookup table // as that only only stores canonicalized paths. Thus, we fallback to // to the canonicalized path and see if that brings up any matches const auto canonicalized = Path(QFileInfo(item->path().toLocalFile()).canonicalFilePath()); it = data.files.constFind(canonicalized); } if (it != data.files.constEnd()) { return *it; } else { // otherwise look for siblings and use the include paths of any we find const Path folder = item->folder() ? item->path() : item->path().parent(); for( it = data.files.constBegin(); it != data.files.constEnd(); ++it) { if (folder.isDirectParentOf(it.key())) { return *it; } } } // last-resort fallback: bubble up the parent chain, and keep looking for include paths if (auto parent = item->parent()) { return fileInformation(parent); } return {}; } Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).includes; } Path::List CMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).frameworkDirectories; } QHash CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const { return fileInformation(item).defines; } KDevelop::IProjectBuilder * CMakeManager::builder() const { IPlugin* i = core()->pluginController()->pluginForExtension( "org.kdevelop.IProjectBuilder", "KDevCMakeBuilder"); Q_ASSERT(i); KDevelop::IProjectBuilder* _builder = i->extension(); Q_ASSERT(_builder ); return _builder ; } bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder) { qCDebug(CMAKE) << "reloading" << folder->path(); IProject* project = folder->project(); if (!project->isReady()) return false; KJob *job = createImportJob(folder); project->setReloadJob(job); ICore::self()->runController()->registerJob( job ); return true; } static void populateTargets(ProjectFolderItem* folder, const QHash& targets) { QStringList dirTargets = targets[folder->path()]; foreach (ProjectTargetItem* item, folder->targetList()) { if(!dirTargets.contains(item->text())) { delete item; } else { dirTargets.removeAll(item->text()); } } static QSet standardTargets = { QStringLiteral("edit_cache"), QStringLiteral("rebuild_cache"), QStringLiteral("list_install_components"), QStringLiteral("test"), //not really standard, but applicable for make and ninja QStringLiteral("install") }; foreach (const QString& name, dirTargets) { if (!name.endsWith(QLatin1String("_automoc")) && !standardTargets.contains(name) && !name.startsWith(QLatin1String("install/")) ) new CMakeTargetItem(folder, name); } foreach (ProjectFolderItem* children, folder->folderList()) { populateTargets(children, targets); } } void CMakeManager::integrateData(const CMakeProjectData &data, KDevelop::IProject* project) { - connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); - connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); + if (data.m_server) { + connect(data.m_server.data(), &CMakeServer::response, project, [this, project](const QJsonObject& response) { + serverResponse(project, response); + }); + } else { + connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); + connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); + } m_projects[project] = data; populateTargets(project->projectItem(), data.targets); CTestUtils::createTestSuites(data.m_testSuites, project); } +void CMakeManager::serverResponse(KDevelop::IProject* project, const QJsonObject& response) +{ + if (response["type"] == QLatin1String("signal")) { + if (response["name"] == QLatin1String("dirty")) { + m_projects[project].m_server->configure({}); + } else + qDebug() << "unhandled signal response..." << project << response; + } else if (response["type"] == QLatin1String("reply")) { + const auto inReplyTo = response["inReplyTo"]; + if (inReplyTo == QLatin1String("configure")) { + m_projects[project].m_server->compute(); + } else if (inReplyTo == QLatin1String("compute")) { + m_projects[project].m_server->codemodel(); + } else if(inReplyTo == QLatin1String("codemodel")) { + auto &data = m_projects[project]; + CMakeServerImportJob::processFileData(response, data); + populateTargets(project->projectItem(), data.targets); + } else { + qDebug() << "unhandled reply response..." << project << response; + } + } else { + qDebug() << "unhandled response..." << project << response; + } +} + // void CMakeManager::deletedWatchedDirectory(IProject* p, const QUrl &dir) // { // if(p->folder().equals(dir, QUrl::CompareWithoutTrailingSlash)) { // ICore::self()->projectController()->closeProject(p); // } else { // if(dir.fileName()=="CMakeLists.txt") { // QList folders = p->foldersForUrl(dir.upUrl()); // foreach(ProjectFolderItem* folder, folders) // reload(folder); // } else { // qDeleteAll(p->itemsForUrl(dir)); // } // } // } // void CMakeManager::directoryChanged(const QString& dir) // { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // void CMakeManager::filesystemBuffererTimeout() // { // Q_FOREACH(const QString& file, m_fileSystemChangedBuffer) { // realDirectoryChanged(file); // } // m_fileSystemChangedBuffer.clear(); // } // void CMakeManager::realDirectoryChanged(const QString& dir) // { // QUrl path(dir); // IProject* p=ICore::self()->projectController()->findProjectForUrl(dir); // if(!p || !p->isReady()) { // if(p) { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // return; // } // // if(!QFile::exists(dir)) { // path.adjustPath(QUrl::AddTrailingSlash); // deletedWatchedDirectory(p, path); // } else // dirtyFile(dir); // } QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const { return folder->targetList(); } QString CMakeManager::name() const { return languageName().str(); } IndexedString CMakeManager::languageName() { static IndexedString name("CMake"); return name; } KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url) { return new CMakeParseJob(url, this); } KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const { return m_highlight; } // ContextMenuExtension CMakeManager::contextMenuExtension( KDevelop::Context* context ) // { // if( context->type() != KDevelop::Context::ProjectItemContext ) // return IPlugin::contextMenuExtension( context ); // // KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); // QList items = ctx->items(); // // if( items.isEmpty() ) // return IPlugin::contextMenuExtension( context ); // // m_clickedItems = items; // ContextMenuExtension menuExt; // if(items.count()==1 && dynamic_cast(items.first())) // { // QAction * action = new QAction( i18n( "Jump to Target Definition" ), this ); // connect( action, SIGNAL(triggered()), this, SLOT(jumpToDeclaration()) ); // menuExt.addAction( ContextMenuExtension::ProjectGroup, action ); // } // // return menuExt; // } // // void CMakeManager::jumpToDeclaration() // { // DUChainAttatched* du=dynamic_cast(m_clickedItems.first()); // if(du) // { // KTextEditor::Cursor c; // QUrl url; // { // KDevelop::DUChainReadLocker lock; // Declaration* decl = du->declaration().data(); // if(!decl) // return; // c = decl->rangeInCurrentRevision().start(); // url = decl->url().toUrl(); // } // // ICore::self()->documentController()->openDocument(url, c); // } // } // // // TODO: Port to Path API // bool CMakeManager::moveFilesAndFolders(const QList< ProjectBaseItem* > &items, ProjectFolderItem* toFolder) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Move files and folders within CMakeLists as follows:")); // // bool cmakeSuccessful = true; // CMakeFolderItem *nearestCMakeFolderItem = nearestCMakeFolder(toFolder); // IProject* project=toFolder->project(); // // QList movedUrls; // QList oldUrls; // foreach(ProjectBaseItem *movedItem, items) // { // QList dirtyItems = cmakeListedItemsAffectedByUrlChange(project, movedItem->url()); // QUrl movedItemNewUrl = toFolder->url(); // movedItemNewUrl.addPath(movedItem->baseName()); // if (movedItem->folder()) // movedItemNewUrl.adjustPath(QUrl::AddTrailingSlash); // foreach(ProjectBaseItem* dirtyItem, dirtyItems) // { // QUrl dirtyItemNewUrl = afterMoveUrl(dirtyItem->url(), movedItem->url(), movedItemNewUrl); // if (CMakeFolderItem* folder = dynamic_cast(dirtyItem)) // { // cmakeSuccessful &= changesWidgetRemoveCMakeFolder(folder, &changesWidget); // cmakeSuccessful &= changesWidgetAddFolder(dirtyItemNewUrl, nearestCMakeFolderItem, &changesWidget); // } // else if (dirtyItem->parent()->target()) // { // cmakeSuccessful &= changesWidgetMoveTargetFile(dirtyItem, dirtyItemNewUrl, &changesWidget); // } // } // // oldUrls += movedItem->url(); // movedUrls += movedItemNewUrl; // } // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort move?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // QList::const_iterator it1=oldUrls.constBegin(), it1End=oldUrls.constEnd(); // QList::const_iterator it2=movedUrls.constBegin(); // Q_ASSERT(oldUrls.size()==movedUrls.size()); // for(; it1!=it1End; ++it1, ++it2) // { // if (!KDevelop::renameUrl(project, *it1, *it2)) // return false; // // QList renamedItems = project->itemsForUrl(*it2); // bool dir = QFileInfo(it2->toLocalFile()).isDir(); // foreach(ProjectBaseItem* item, renamedItems) { // if(dir) // emit folderRenamed(Path(*it1), item->folder()); // else // emit fileRenamed(Path(*it1), item->file()); // } // } // // return true; // } // // bool CMakeManager::copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* toFolder) // { // IProject* project = toFolder->project(); // foreach(const Path& path, items) { // if (!KDevelop::copyUrl(project, path.toUrl(), toFolder->url())) // return false; // } // // return true; // } // // bool CMakeManager::removeFilesAndFolders(const QList &items) // { // using namespace CMakeEdit; // // IProject* p = 0; // QList urls; // foreach(ProjectBaseItem* item, items) // { // Q_ASSERT(item->folder() || item->file()); // // urls += item->url(); // if(!p) // p = item->project(); // } // // //First do CMakeLists changes // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Remove files and folders from CMakeLists as follows:")); // // bool cmakeSuccessful = changesWidgetRemoveItems(cmakeListedItemsAffectedByItemsChanged(items).toSet(), &changesWidget); // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort deletion?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = true; // //Then delete the files/folders // foreach(const QUrl& file, urls) // { // ret &= KDevelop::removeUrl(p, file, QDir(file.toLocalFile()).exists()); // } // // return ret; // } bool CMakeManager::removeFilesFromTargets(const QList &/*files*/) { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify project targets as follows:")); // // if (!files.isEmpty() && // changesWidgetRemoveFilesFromTargets(files, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges()) { // return true; // } return false; } // ProjectFolderItem* CMakeManager::addFolder(const Path& folder, ProjectFolderItem* parent) // { // using namespace CMakeEdit; // // CMakeFolderItem *cmakeParent = nearestCMakeFolder(parent); // if(!cmakeParent) // return 0; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Create folder '%1':", folder.lastPathSegment())); // // ///FIXME: use path in changes widget // changesWidgetAddFolder(folder.toUrl(), cmakeParent, &changesWidget); // // if(changesWidget.exec() && changesWidget.applyAllChanges()) // { // if(KDevelop::createFolder(folder.toUrl())) { //If saved we create the folder then the CMakeLists.txt file // Path newCMakeLists(folder, "CMakeLists.txt"); // KDevelop::createFile( newCMakeLists.toUrl() ); // } else // KMessageBox::error(0, i18n("Could not save the change."), // DIALOG_CAPTION); // } // // return 0; // } // // KDevelop::ProjectFileItem* CMakeManager::addFile( const Path& file, KDevelop::ProjectFolderItem* parent) // { // KDevelop::ProjectFileItem* created = 0; // if ( KDevelop::createFile(file.toUrl()) ) { // QList< ProjectFileItem* > files = parent->project()->filesForPath(IndexedString(file.pathOrUrl())); // if(!files.isEmpty()) // created = files.first(); // else // created = new KDevelop::ProjectFileItem( parent->project(), file, parent ); // } // return created; // } bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/) { return false; // using namespace CMakeEdit; // // const QSet headerExt = QSet() << ".h" << ".hpp" << ".hxx"; // QList< ProjectFileItem* > files = _files; // for (int i = files.count() - 1; i >= 0; --i) // { // QString fileName = files[i]->fileName(); // QString fileExt = fileName.mid(fileName.lastIndexOf('.')); // QList sameUrlItems = files[i]->project()->itemsForUrl(files[i]->url()); // if (headerExt.contains(fileExt)) // files.removeAt(i); // else foreach(ProjectBaseItem* item, sameUrlItems) // { // if (item->parent() == target) // { // files.removeAt(i); // break; // } // } // } // // if(files.isEmpty()) // return true; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify target '%1' as follows:", target->baseName())); // // bool success = changesWidgetAddFilesToTarget(files, target, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges(); // // if(!success) // KMessageBox::error(0, i18n("CMakeLists changes failed."), DIALOG_CAPTION); // // return success; } // bool CMakeManager::renameFileOrFolder(ProjectBaseItem *item, const Path &newPath) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Rename '%1' to '%2':", item->text(), // newPath.lastPathSegment())); // // bool cmakeSuccessful = true, changedCMakeLists=false; // IProject* project=item->project(); // const Path oldPath=item->path(); // QUrl oldUrl=oldPath.toUrl(); // if (item->file()) // { // QList targetFiles = cmakeListedItemsAffectedByUrlChange(project, oldUrl); // foreach(ProjectBaseItem* targetFile, targetFiles) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetMoveTargetFile(targetFile, newPath.toUrl(), &changesWidget); // } // else if (CMakeFolderItem *folder = dynamic_cast(item)) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetRenameFolder(folder, newPath.toUrl(), &changesWidget); // // item->setPath(newPath); // if (changesWidget.hasDocuments() && cmakeSuccessful) { // changedCMakeLists = changesWidget.exec() && changesWidget.applyAllChanges(); // cmakeSuccessful &= changedCMakeLists; // } // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort rename?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = KDevelop::renameUrl(project, oldUrl, newPath.toUrl()); // if(!ret) { // item->setPath(oldPath); // } // return ret; // } // // bool CMakeManager::renameFile(ProjectFileItem *item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } // // bool CMakeManager::renameFolder(ProjectFolderItem* item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } QWidget* CMakeManager::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) { KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url)); Declaration *decl=nullptr; if(top) { int useAt=top->findUseAt(top->transformToLocalRevision(position)); if(useAt>=0) { Use u=top->uses()[useAt]; decl=u.usedDeclaration(top->topContext()); } } CMakeNavigationWidget* doc=nullptr; if(decl) { doc=new CMakeNavigationWidget(top, decl); } else { const IDocument* d=ICore::self()->documentController()->documentForUrl(url); const KTextEditor::Document* e=d->textDocument(); KTextEditor::Cursor start=position, end=position, step(0,1); for(QChar i=e->characterAt(start); i.isLetter() || i=='_'; i=e->characterAt(start-=step)) {} start+=step; for(QChar i=e->characterAt(end); i.isLetter() || i=='_'; i=e->characterAt(end+=step)) {} QString id=e->text(KTextEditor::Range(start, end)); ICMakeDocumentation* docu=CMake::cmakeDocumentation(); if( docu ) { IDocumentation::Ptr desc=docu->description(id, url); if(desc) { doc=new CMakeNavigationWidget(top, desc); } } } return doc; } QPair CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const { return QPair(); } // { // QPair ret; // if(project==0 && !m_projectsData.isEmpty()) // { // project=m_projectsData.keys().first(); // } // // // qCDebug(CMAKE) << "cache value " << id << project << (m_projectsData.contains(project) && m_projectsData[project].cache.contains(id)); // CMakeProjectData* data = m_projectsData[project]; // if(data && data->cache.contains(id)) // { // const CacheEntry& e=data->cache.value(id); // ret.first=e.value; // ret.second=e.doc; // } // return ret; // }Add // void CMakeManager::projectClosing(IProject* p) { m_projects.remove(p); // delete m_projectsData.take(p); // delete m_watchers.take(p); // // m_filter->remove(p); // // qCDebug(CMAKE) << "Project closed" << p; } // // QStringList CMakeManager::processGeneratorExpression(const QStringList& expr, IProject* project, ProjectTargetItem* target) const // { // QStringList ret; // const CMakeProjectData* data = m_projectsData[project]; // GenerationExpressionSolver exec(data->properties, data->targetAlias); // if(target) // exec.setTargetName(target->text()); // // exec.defineVariable("INSTALL_PREFIX", data->vm.value("CMAKE_INSTALL_PREFIX").join(QString())); // for(QStringList::const_iterator it = expr.constBegin(), itEnd = expr.constEnd(); it!=itEnd; ++it) { // QStringList val = exec.run(*it).split(';'); // ret += val; // } // return ret; // } /* void CMakeManager::addPending(const Path& path, CMakeFolderItem* folder) { m_pending.insert(path, folder); } CMakeFolderItem* CMakeManager::takePending(const Path& path) { return m_pending.take(path); } void CMakeManager::addWatcher(IProject* p, const QString& path) { if (QFileSystemWatcher* watcher = m_watchers.value(p)) { watcher->addPath(path); } else { qWarning() << "Could not find a watcher for project" << p << p->name() << ", path " << path; Q_ASSERT(false); } }*/ // CMakeProjectData CMakeManager::projectData(IProject* project) // { // Q_ASSERT(QThread::currentThread() == project->thread()); // CMakeProjectData* data = m_projectsData[project]; // if(!data) { // data = new CMakeProjectData; // m_projectsData[project] = data; // } // return *data; // } ProjectFilterManager* CMakeManager::filterManager() const { return m_filter; } void CMakeManager::dirtyFile(const QString& path) { qCDebug(CMAKE) << "dirty!" << path; //we initialize again hte project that sent the signal for(QHash::const_iterator it = m_projects.constBegin(), itEnd = m_projects.constEnd(); it!=itEnd; ++it) { if(it->watcher == sender()) { reload(it.key()->projectItem()); break; } } } void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder) { populateTargets(folder, m_projects[folder->project()].targets); } ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO: when we have data about targets, use folders with targets or similar if (QFile::exists(path.toLocalFile()+"/CMakeLists.txt")) return new KDevelop::ProjectBuildFolderItem( project, path, parent ); else return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent); } int CMakeManager::perProjectConfigPages() const { return 1; } ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CMakePreferences(this, options, parent); } return nullptr; } #include "cmakemanager.moc" diff --git a/projectmanagers/cmake/cmakemanager.h b/projectmanagers/cmake/cmakemanager.h index 195630f251..bc77b18b73 100644 --- a/projectmanagers/cmake/cmakemanager.h +++ b/projectmanagers/cmake/cmakemanager.h @@ -1,164 +1,164 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2009 Aleix Pol * * 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 CMAKEMANAGER_H #define CMAKEMANAGER_H #include #include #include #include #include #include #include #include #include "cmakeprojectdata.h" #include "icmakemanager.h" class WaitAllJobs; class CMakeCommitChangesJob; struct CMakeProjectData; class QObject; class CMakeHighlighting; class CMakeDocumentation; namespace KDevelop { class IProject; class IProjectBuilder; class ICodeHighlighting; class ProjectFolderItem; class ProjectBaseItem; class ProjectFileItem; class ProjectTargetItem; class ProjectFilterManager; class IProjectFilter; class ParseJob; class ContextMenuExtension; class Context; } class CMakeFolderItem; class CMakeManager : public KDevelop::AbstractFileManagerPlugin , public KDevelop::IBuildSystemManager , public KDevelop::ILanguageSupport , public ICMakeManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) Q_INTERFACES( KDevelop::IProjectFileManager ) Q_INTERFACES( KDevelop::ILanguageSupport ) Q_INTERFACES( ICMakeManager ) public: explicit CMakeManager( QObject* parent = nullptr, const QVariantList& args = QVariantList() ); ~CMakeManager() override; Features features() const override { return Features(Folders | Targets | Files ); } KDevelop::IProjectBuilder* builder() const override; bool hasBuildInfo(KDevelop::ProjectBaseItem*) const override; KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem *) const override; KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem *item) const override; QHash defines(KDevelop::ProjectBaseItem *) const override; KDevelop::ProjectTargetItem* createTarget( const QString&, KDevelop::ProjectFolderItem* ) override { return nullptr; } virtual QList targets() const; QList targets(KDevelop::ProjectFolderItem* folder) const override; // virtual KDevelop::ProjectFolderItem* addFolder( const KDevelop::Path& folder, KDevelop::ProjectFolderItem* parent ); // virtual KDevelop::ProjectFileItem* addFile( const KDevelop::Path&, KDevelop::ProjectFolderItem* ); bool addFilesToTarget( const QList &files, KDevelop::ProjectTargetItem* target) override; bool removeTarget( KDevelop::ProjectTargetItem* ) override { return false; } bool removeFilesFromTargets( const QList &files ) override; // virtual bool removeFilesAndFolders( const QList &items); // // virtual bool renameFile(KDevelop::ProjectFileItem*, const KDevelop::Path&); // virtual bool renameFolder(KDevelop::ProjectFolderItem*, const KDevelop::Path&); // virtual bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &items, KDevelop::ProjectFolderItem *newParent); // virtual bool copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* newParent); // // virtual QList parse( KDevelop::ProjectFolderItem* dom ); KDevelop::ProjectFolderItem* import( KDevelop::IProject *project ) override; KJob* createImportJob(KDevelop::ProjectFolderItem* item) override; // bool reload(KDevelop::ProjectFolderItem*) override; // // virtual KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* context ); KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = nullptr) override; QPair cacheValue(KDevelop::IProject* project, const QString& id) const override; //LanguageSupport QString name() const override; KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &url) override; KDevelop::ICodeHighlighting* codeHighlighting() const override; QWidget* specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) override; // void addPending(const KDevelop::Path& path, CMakeFolderItem* folder); // CMakeFolderItem* takePending(const KDevelop::Path& path); // void addWatcher(KDevelop::IProject* p, const QString& path); // CMakeProjectData projectData(KDevelop::IProject* project); KDevelop::ProjectFilterManager* filterManager() const; static KDevelop::IndexedString languageName(); int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; void integrateData(const CMakeProjectData &data, KDevelop::IProject* project); signals: void folderRenamed(const KDevelop::Path& oldFolder, KDevelop::ProjectFolderItem* newFolder); void fileRenamed(const KDevelop::Path& oldFile, KDevelop::ProjectFileItem* newFile); private slots: -// void dirtyFile(const QString& file); -// + void serverResponse(KDevelop::IProject* project, const QJsonObject &value); + // void jumpToDeclaration(); void projectClosing(KDevelop::IProject*); void dirtyFile(const QString& file); // // void directoryChanged(const QString& dir); // void filesystemBuffererTimeout(); private: CMakeFile fileInformation(KDevelop::ProjectBaseItem* item) const; void folderAdded(KDevelop::ProjectFolderItem* folder); QHash m_projects; KDevelop::ProjectFilterManager* m_filter; KDevelop::ICodeHighlighting* m_highlight; }; #endif diff --git a/projectmanagers/cmake/cmakeserverimportjob.cpp b/projectmanagers/cmake/cmakeserverimportjob.cpp index a414162df4..c7e1023bff 100644 --- a/projectmanagers/cmake/cmakeserverimportjob.cpp +++ b/projectmanagers/cmake/cmakeserverimportjob.cpp @@ -1,179 +1,180 @@ /* KDevelop CMake Support * * Copyright 2017 Aleix Pol Gonzalez * * 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. */ #include "cmakeserverimportjob.h" #include "cmakeutils.h" #include "cmakeserver.h" #include #include #include #include #include #include #include "debug.h" template static T kTransform(const Q& list, W func) { T ret; ret.reserve(list.size()); for (auto it = list.constBegin(), itEnd = list.constEnd(); it!=itEnd; ++it) ret += func(*it); return ret; } static QString unescape(const QStringRef& input) { QString output; output.reserve(input.length()); bool isEscaped = false; for (auto it = input.data(), end = it + input.length(); it != end; ++it) { QChar c = *it; if (!isEscaped && c == '\\') { isEscaped = true; } else { output.append(c); isEscaped = false; } } return output; } static QHash processDefines(const QString &compileFlags, const QJsonArray &defines) { QHash ret; const auto& defineRx = MakeFileResolver::defineRegularExpression(); auto it = defineRx.globalMatch(compileFlags); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret[match.captured(1)] = value; } for (const QJsonValue& defineValue: defines) { const QString define = defineValue.toString(); const int eqIdx = define.indexOf(QLatin1Char('=')); if (eqIdx<0) { ret[define] = QString(); } else { ret[define.left(eqIdx)] = define.mid(eqIdx+1); } } return ret; } -static void processFileData(const QJsonObject &response, CMakeProjectData &data) +void CMakeServerImportJob::processFileData(const QJsonObject &response, CMakeProjectData &data) { const auto configs = response.value(QLatin1String("configurations")).toArray(); qCDebug(CMAKE) << "process response" << response; for (const auto &config: configs) { const auto projects = config.toObject().value(QLatin1String("projects")).toArray(); for (const auto &project: projects) { const auto targets = project.toObject().value(QLatin1String("targets")).toArray(); for (const auto &targetObject: targets) { const auto target = targetObject.toObject(); const KDevelop::Path targetDir(target.value(QLatin1String("sourceDirectory")).toString()); data.targets[targetDir] += target.value(QLatin1String("name")).toString(); const auto fileGroups = target.value(QLatin1String("fileGroups")).toArray(); for (const auto &fileGroupValue: fileGroups) { const auto fileGroup = fileGroupValue.toObject(); CMakeFile file; file.includes = kTransform(fileGroup.value(QLatin1String("includePath")).toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toObject().value(QLatin1String("path")).toString()); }); file.defines = processDefines(fileGroup.value(QLatin1String("compileFlags")).toString(), fileGroup.value(QLatin1String("defines")).toArray()); const auto sourcesArray = fileGroup.value(QLatin1String("sources")).toArray(); const KDevelop::Path::List sources = kTransform(sourcesArray, [targetDir](const QJsonValue& val) { return KDevelop::Path(targetDir, val.toString()); }); for (const auto& source: sources) { // NOTE: we use the canonical file path to prevent issues with symlinks in the path // leading to lookup failures const auto localFile = source.toLocalFile(); const auto canonicalFile = QFileInfo(source.toLocalFile()).canonicalFilePath(); const auto sourcePath = localFile == canonicalFile ? source : KDevelop::Path(canonicalFile); data.compilationData.files[sourcePath] = file; } qCDebug(CMAKE) << "registering..." << sources << file; } } } } } CMakeServerImportJob::CMakeServerImportJob(KDevelop::IProject* project, CMakeServer* server, QObject* parent) : KJob(parent) , m_server(server) , m_project(project) { connect(m_server.data(), &CMakeServer::disconnected, this, [this]() { setError(UnexpectedDisconnect); emitResult(); }); } void CMakeServerImportJob::start() { if (m_server->isServerAvailable()) doStart(); else connect(m_server.data(), &CMakeServer::connected, this, &CMakeServerImportJob::doStart); } void CMakeServerImportJob::doStart() { connect(m_server.data(), &CMakeServer::response, this, &CMakeServerImportJob::processResponse); m_server->handshake(m_project->path(), CMake::currentBuildDir(m_project)); } void CMakeServerImportJob::processResponse(const QJsonObject& response) { const auto responseType = response.value(QLatin1String("type")); if (responseType == QLatin1String("reply")) { const auto inReplyTo = response.value(QLatin1String("inReplyTo")); qCDebug(CMAKE) << "replying..." << inReplyTo; if (inReplyTo == QLatin1String("handshake")) { m_server->configure({}); } else if (inReplyTo == QLatin1String("configure")) { m_server->compute(); } else if (inReplyTo == QLatin1String("compute")) { m_server->codemodel(); } else if(inReplyTo == QLatin1String("codemodel")) { processFileData(response, m_data); + m_data.m_server = m_server; emitResult(); } else { qWarning() << "unhandled reply" << response; } } else if(responseType == QLatin1String("error")) { setError(ErrorResponse); setErrorText(response.value(QLatin1String("errorMessage")).toString()); qWarning() << "error!!" << response; emitResult(); } else { qWarning() << "unhandled message" << response; } } diff --git a/projectmanagers/cmake/cmakeserverimportjob.h b/projectmanagers/cmake/cmakeserverimportjob.h index 059d249453..42238912c0 100644 --- a/projectmanagers/cmake/cmakeserverimportjob.h +++ b/projectmanagers/cmake/cmakeserverimportjob.h @@ -1,57 +1,59 @@ /* KDevelop CMake Support * * Copyright 2017 Aleix Pol Gonzalez * * 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 CMAKESERVERIMPORTJOB_H #define CMAKESERVERIMPORTJOB_H #include #include #include "cmakeprojectdata.h" namespace KDevelop { class IProject; } class CMakeServerImportJob : public KJob { Q_OBJECT public: CMakeServerImportJob(KDevelop::IProject* project, CMakeServer* server, QObject* parent); enum Error { NoError, UnexpectedDisconnect, ErrorResponse }; void start() override; KDevelop::IProject* project() const { return m_project; } CMakeProjectData projectData() const { return m_data; } + static void processFileData(const QJsonObject &response, CMakeProjectData &data); + private: void doStart(); void processResponse(const QJsonObject &response); - QPointer m_server; + QSharedPointer m_server; KDevelop::IProject* m_project; CMakeProjectData m_data; }; #endif // CMAKESERVERIMPORTJOB_H