/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Qt Software Information (qt-info@nokia.com) ** ** Commercial Usage ** ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** **************************************************************************/ #include "cpptoolsplugin.h" #include "completionsettingspage.h" #include "cppfilesettingspage.h" #include "cppclassesfilter.h" #include "cppcodecompletion.h" #include "cppfunctionsfilter.h" #include "cppmodelmanager.h" #include "cpptoolsconstants.h" #include "cppquickopenfilter.h" #include <extensionsystem/pluginmanager.h> #include <coreplugin/icore.h> #include <coreplugin/mimedatabase.h> #include <coreplugin/coreconstants.h> #include <coreplugin/uniqueidmanager.h> #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/progressmanager/progressmanager.h> #include <cppeditor/cppeditorconstants.h> #include <QtCore/QtConcurrentRun> #include <QtCore/QFutureSynchronizer> #include <qtconcurrent/runextensions.h> #include <find/ifindfilter.h> #include <find/searchresultwindow.h> #include <utils/filesearch.h> #include <Control.h> #include <AST.h> #include <ASTVisitor.h> #include <TranslationUnit.h> #include <PrettyPrinter.h> #include <cplusplus/PreprocessorEnvironment.h> #include <cplusplus/pp.h> #include <QtCore/QtPlugin> #include <QtCore/QFileInfo> #include <QtCore/QDir> #include <QtCore/QDebug> #include <QtCore/QSettings> #include <QtGui/QMenu> #include <QtGui/QAction> #include <sstream> using namespace CppTools::Internal; using namespace CPlusPlus; enum { debug = 0 }; CppToolsPlugin *CppToolsPlugin::m_instance = 0; namespace { class SimpleClient: public Client { Environment _env; QPointer<CppModelManager> _modelManager; Snapshot _snapshot; Preprocessor _preproc; QSet<QString> _merged; public: SimpleClient(QPointer<CppModelManager> modelManager) : _modelManager(modelManager), _snapshot(_modelManager->snapshot()), _preproc(this, &_env) { } QByteArray run(QString fileName, const QByteArray &source) { const QByteArray preprocessed = _preproc(fileName, source); return preprocessed; } virtual void sourceNeeded(QString &fileName, IncludeType, unsigned) { mergeEnvironment(fileName); } virtual void macroAdded(const Macro &) {} virtual void startExpandingMacro(unsigned, const Macro &, const QByteArray &, const QVector<MacroArgumentReference> &) {} virtual void stopExpandingMacro(unsigned, const Macro &) {} virtual void startSkippingBlocks(unsigned) {} virtual void stopSkippingBlocks(unsigned) {} void mergeEnvironment(const QString &fileName) { if (! _merged.contains(fileName)) { _merged.insert(fileName); if (Document::Ptr doc = _snapshot.value(fileName)) { foreach (const Document::Include &i, doc->includes()) mergeEnvironment(i.fileName()); _env.addMacros(doc->definedMacros()); } } } }; class FindClass: protected ASTVisitor { QFutureInterface<Core::Utils::FileSearchResult> &_future; Document::Ptr _doc; Snapshot _snapshot; QByteArray _source; QString _text; QTextDocument::FindFlags _findFlags; public: FindClass(QFutureInterface<Core::Utils::FileSearchResult> &future, Document::Ptr doc, Snapshot snapshot) : ASTVisitor(doc->control()), _future(future), _doc(doc), _snapshot(snapshot) { } void operator()(AST *ast, const QByteArray &source, const QString &text, QTextDocument::FindFlags findFlags) { _source = source; _text = text; _findFlags = findFlags; accept(ast); } protected: using ASTVisitor::visit; virtual bool visit(ClassSpecifierAST *ast) { if (ast->name) { Qt::CaseSensitivity cs = Qt::CaseInsensitive; if (_findFlags & QTextDocument::FindCaseSensitively) cs = Qt::CaseSensitive; Token start = tokenAt(ast->name->firstToken()); Token end = tokenAt(ast->name->lastToken() - 1); const QString className = QString::fromUtf8(_source.constData() + start.begin(), end.end() - start.begin()); int idx = className.indexOf(_text, cs); if (idx != -1) { const char *beg = _source.constData(); const char *cp = beg + start.offset; for (; cp != beg - 1; --cp) { if (*cp == '\n') break; } ++cp; const char *lineEnd = cp + 1; for (; *lineEnd; ++lineEnd) { if (*lineEnd == '\n') break; } const QString matchingLine = QString::fromUtf8(cp, lineEnd - cp); unsigned line, col; getTokenStartPosition(ast->name->firstToken(), &line, &col); _future.reportResult(Core::Utils::FileSearchResult(QDir::toNativeSeparators(_doc->fileName()), line, matchingLine, col + idx - 1, _text.length())); } } return true; } }; static void searchClassDeclarations(QFutureInterface<Core::Utils::FileSearchResult> &future, QPointer<CppModelManager> modelManager, QString text, QTextDocument::FindFlags findFlags) { const Snapshot snapshot = modelManager->snapshot(); future.setProgressRange(0, snapshot.size()); future.setProgressValue(0); int progress = 0; foreach (Document::Ptr doc, snapshot) { const QString fileName = doc->fileName(); QFile file(fileName); if (! file.open(QFile::ReadOnly)) continue; const QString contents = QTextStream(&file).readAll(); SimpleClient r(modelManager); const QByteArray source = r.run(fileName, contents.toUtf8()); Document::Ptr newDoc = Document::create(fileName); newDoc->setSource(source); newDoc->parse(); FindClass findClass(future, newDoc, snapshot); findClass(newDoc->translationUnit()->ast(), source, text, findFlags); future.setProgressValue(++progress); } } } // end of anonymous namespace FindClassDeclarations::FindClassDeclarations(CppModelManager *modelManager) : _modelManager(modelManager), _resultWindow(ExtensionSystem::PluginManager::instance()->getObject<Find::SearchResultWindow>()) { m_watcher.setPendingResultsLimit(1); connect(&m_watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int))); connect(&m_watcher, SIGNAL(finished()), this, SLOT(searchFinished())); } void FindClassDeclarations::findAll(const QString &txt, QTextDocument::FindFlags findFlags) { _resultWindow->clearContents(); _resultWindow->popup(true); Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager(); QFuture<Core::Utils::FileSearchResult> result = QtConcurrent::run(&searchClassDeclarations, _modelManager, txt, findFlags); m_watcher.setFuture(result); Core::FutureProgress *progress = progressManager->addTask(result, tr("Search class"), CppTools::Constants::TASK_INDEX, Core::ProgressManager::CloseOnSuccess); connect(progress, SIGNAL(clicked()), _resultWindow, SLOT(popup())); } void FindClassDeclarations::displayResult(int index) { Core::Utils::FileSearchResult result = m_watcher.future().resultAt(index); Find::ResultWindowItem *item = _resultWindow->addResult(result.fileName, result.lineNumber, result.matchingLine, result.matchStart, result.matchLength); if (item) connect(item, SIGNAL(activated(const QString&,int,int)), this, SLOT(openEditor(const QString&,int,int))); } void FindClassDeclarations::searchFinished() { emit changed(); } void FindClassDeclarations::openEditor(const QString &fileName, int line, int column) { TextEditor::BaseTextEditor::openEditorAt(fileName, line, column); } CppToolsPlugin::CppToolsPlugin() : m_context(-1), m_modelManager(0), m_fileSettings(new CppFileSettings) { m_instance = this; } CppToolsPlugin::~CppToolsPlugin() { m_instance = 0; m_modelManager = 0; // deleted automatically } bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error) { Q_UNUSED(arguments); Q_UNUSED(error); Core::ICore *core = Core::ICore::instance(); Core::ActionManager *am = core->actionManager(); // Objects m_modelManager = new CppModelManager(this); addAutoReleasedObject(m_modelManager); m_completion = new CppCodeCompletion(m_modelManager); addAutoReleasedObject(m_completion); CppQuickOpenFilter *quickOpenFilter = new CppQuickOpenFilter(m_modelManager, core->editorManager()); addAutoReleasedObject(quickOpenFilter); addAutoReleasedObject(new CppClassesFilter(m_modelManager, core->editorManager())); addAutoReleasedObject(new CppFunctionsFilter(m_modelManager, core->editorManager())); addAutoReleasedObject(new CompletionSettingsPage(m_completion)); addAutoReleasedObject(new CppFileSettingsPage(m_fileSettings)); addAutoReleasedObject(new FindClassDeclarations(m_modelManager)); // Menus Core::ActionContainer *mtools = am->actionContainer(Core::Constants::M_TOOLS); Core::ActionContainer *mcpptools = am->createMenu(CppTools::Constants::M_TOOLS_CPP); QMenu *menu = mcpptools->menu(); menu->setTitle(tr("&C++")); menu->setEnabled(true); mtools->addMenu(mcpptools); // Actions m_context = core->uniqueIDManager()->uniqueIdentifier(CppEditor::Constants::C_CPPEDITOR); QList<int> context = QList<int>() << m_context; QAction *switchAction = new QAction(tr("Switch Header/Source"), this); Core::Command *command = am->registerAction(switchAction, Constants::SWITCH_HEADER_SOURCE, context); command->setDefaultKeySequence(QKeySequence(Qt::Key_F4)); mcpptools->addAction(command); connect(switchAction, SIGNAL(triggered()), this, SLOT(switchHeaderSource())); // Restore settings QSettings *settings = Core::ICore::instance()->settings(); settings->beginGroup(QLatin1String("CppTools")); settings->beginGroup(QLatin1String("Completion")); const bool caseSensitive = settings->value(QLatin1String("CaseSensitive"), true).toBool(); m_completion->setCaseSensitivity(caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); m_completion->setAutoInsertBrackets(settings->value(QLatin1String("AutoInsertBraces"), true).toBool()); m_completion->setPartialCompletionEnabled(settings->value(QLatin1String("PartiallyComplete"), true).toBool()); settings->endGroup(); settings->endGroup(); return true; } void CppToolsPlugin::extensionsInitialized() { // The Cpp editor plugin, which is loaded later on, registers the Cpp mime types, // so, apply settings here m_fileSettings->fromSettings(Core::ICore::instance()->settings()); if (!m_fileSettings->applySuffixesToMimeDB()) qWarning("Unable to apply cpp suffixes to mime database (cpp mime types not found).\n"); } void CppToolsPlugin::shutdown() { // Save settings QSettings *settings = Core::ICore::instance()->settings(); settings->beginGroup(QLatin1String("CppTools")); settings->beginGroup(QLatin1String("Completion")); settings->setValue(QLatin1String("CaseSensitive"), m_completion->caseSensitivity() == Qt::CaseSensitive); settings->setValue(QLatin1String("AutoInsertBraces"), m_completion->autoInsertBrackets()); settings->setValue(QLatin1String("PartiallyComplete"), m_completion->isPartialCompletionEnabled()); settings->endGroup(); settings->endGroup(); } void CppToolsPlugin::switchHeaderSource() { Core::EditorManager *editorManager = Core::EditorManager::instance(); Core::IEditor *editor = editorManager->currentEditor(); QString otherFile = correspondingHeaderOrSource(editor->file()->fileName()); if (!otherFile.isEmpty()) { editorManager->openEditor(otherFile); editorManager->ensureEditorManagerVisible(); } } QFileInfo CppToolsPlugin::findFile(const QDir &dir, const QString &name, const ProjectExplorer::Project *project) const { if (debug) qDebug() << Q_FUNC_INFO << dir << name; QFileInfo fileInSameDir(dir, name); if (project && !fileInSameDir.isFile()) { QString pattern = QString(1, QLatin1Char('/')); pattern += name; const QStringList projectFiles = project->files(ProjectExplorer::Project::AllFiles); const QStringList::const_iterator pcend = projectFiles.constEnd(); for (QStringList::const_iterator it = projectFiles.constBegin(); it != pcend; ++it) if (it->endsWith(pattern)) return QFileInfo(*it); return QFileInfo(); } return fileInSameDir; } // Figure out file type enum FileType { HeaderFile, C_SourceFile, CPP_SourceFile, ObjectiveCPP_SourceFile, UnknownType }; static inline FileType fileType(const Core::MimeDatabase *mimeDatase, const QFileInfo & fi) { const Core::MimeType mimeType = mimeDatase->findByFile(fi); if (!mimeType) return UnknownType; const QString typeName = mimeType.type(); if (typeName == QLatin1String(CppTools::Constants::C_SOURCE_MIMETYPE)) return C_SourceFile; if (typeName == QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)) return CPP_SourceFile; if (typeName == QLatin1String(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE)) return ObjectiveCPP_SourceFile; if (typeName == QLatin1String(CppTools::Constants::C_HEADER_MIMETYPE) || typeName == QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)) return HeaderFile; return UnknownType; } // Return the suffixes that should be checked when trying to find a // source belonging to a header and vice versa static QStringList matchingCandidateSuffixes(const Core::MimeDatabase *mimeDatase, FileType type) { switch (type) { case UnknownType: break; case HeaderFile: // Note that C/C++ headers are undistinguishable return mimeDatase->findByType(QLatin1String(CppTools::Constants::C_SOURCE_MIMETYPE)).suffixes() + mimeDatase->findByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)).suffixes() + mimeDatase->findByType(QLatin1String(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE)).suffixes(); case C_SourceFile: return mimeDatase->findByType(QLatin1String(CppTools::Constants::C_HEADER_MIMETYPE)).suffixes(); case CPP_SourceFile: case ObjectiveCPP_SourceFile: return mimeDatase->findByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)).suffixes(); } return QStringList(); } QString CppToolsPlugin::correspondingHeaderOrSourceI(const QString &fileName) const { const Core::ICore *core = Core::ICore::instance(); const Core::MimeDatabase *mimeDatase = core->mimeDatabase(); ProjectExplorer::ProjectExplorerPlugin *explorer = ProjectExplorer::ProjectExplorerPlugin::instance(); ProjectExplorer::Project *project = (explorer ? explorer->currentProject() : 0); const QFileInfo fi(fileName); const FileType type = fileType(mimeDatase, fi); if (debug) qDebug() << Q_FUNC_INFO << fileName << type; if (type == UnknownType) return QString(); const QDir absoluteDir = fi.absoluteDir(); const QString baseName = fi.completeBaseName(); const QStringList suffixes = matchingCandidateSuffixes(mimeDatase, type); const QString privateHeaderSuffix = QLatin1String("_p"); const QChar dot = QLatin1Char('.'); QStringList candidates; // Check base matches 'source.h'-> 'source.cpp' and vice versa const QStringList::const_iterator scend = suffixes.constEnd(); for (QStringList::const_iterator it = suffixes.constBegin(); it != scend; ++it) { QString candidate = baseName; candidate += dot; candidate += *it; const QFileInfo candidateFi = findFile(absoluteDir, candidate, project); if (candidateFi.isFile()) return candidateFi.absoluteFilePath(); } if (type == HeaderFile) { // 'source_p.h': try 'source.cpp' if (baseName.endsWith(privateHeaderSuffix)) { QString sourceBaseName = baseName; sourceBaseName.truncate(sourceBaseName.size() - privateHeaderSuffix.size()); for (QStringList::const_iterator it = suffixes.constBegin(); it != scend; ++it) { QString candidate = sourceBaseName; candidate += dot; candidate += *it; const QFileInfo candidateFi = findFile(absoluteDir, candidate, project); if (candidateFi.isFile()) return candidateFi.absoluteFilePath(); } } } else { // 'source.cpp': try 'source_p.h' const QStringList::const_iterator scend = suffixes.constEnd(); for (QStringList::const_iterator it = suffixes.constBegin(); it != scend; ++it) { QString candidate = baseName; candidate += privateHeaderSuffix; candidate += dot; candidate += *it; const QFileInfo candidateFi = findFile(absoluteDir, candidate, project); if (candidateFi.isFile()) return candidateFi.absoluteFilePath(); } } return QString(); } QString CppToolsPlugin::correspondingHeaderOrSource(const QString &fileName) const { const QString rc = correspondingHeaderOrSourceI(fileName); if (debug) qDebug() << Q_FUNC_INFO << fileName << rc; return rc; } Q_EXPORT_PLUGIN(CppToolsPlugin)