From adee8336bbc1365149dbcd33b0e05a2096c4dfa8 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Sun, 25 Aug 2013 22:42:37 +0300 Subject: [PATCH] C++: Custom directory list for Switch Header/Source Some projects use separate directories for sources and headers. An example tree: * |-- src |-- foo.cpp |-- include |-- foo.h Allow the user to specify directories for finding out-of-project related header/source files, in addition to current directory Task-number: QTCREATORBUG-8883 Change-Id: I57215c8f2feffcc246d0d161798290861bcfcdd4 Reviewed-by: Nikolai Kosjar --- src/plugins/cpptools/cppfilesettingspage.cpp | 39 +++- src/plugins/cpptools/cppfilesettingspage.h | 2 + src/plugins/cpptools/cppfilesettingspage.ui | 167 ++++++++++++++---- src/plugins/cpptools/cppheadersource_test.cpp | 72 ++++++++ src/plugins/cpptools/cpptools.pro | 3 +- src/plugins/cpptools/cpptools.qbs | 1 + src/plugins/cpptools/cpptoolsplugin.cpp | 55 +++++- src/plugins/cpptools/cpptoolsplugin.h | 6 + tests/cppheadersource/includesub/foo.cpp | 1 + .../cppheadersource/includesub/include/foo.h | 1 + tests/cppheadersource/samedir/foo.cpp | 1 + tests/cppheadersource/samedir/foo.h | 1 + 12 files changed, 300 insertions(+), 49 deletions(-) create mode 100644 src/plugins/cpptools/cppheadersource_test.cpp create mode 100644 tests/cppheadersource/includesub/foo.cpp create mode 100644 tests/cppheadersource/includesub/include/foo.h create mode 100644 tests/cppheadersource/samedir/foo.cpp create mode 100644 tests/cppheadersource/samedir/foo.h diff --git a/src/plugins/cpptools/cppfilesettingspage.cpp b/src/plugins/cpptools/cppfilesettingspage.cpp index e11cfdf55ea..9de31210d82 100644 --- a/src/plugins/cpptools/cppfilesettingspage.cpp +++ b/src/plugins/cpptools/cppfilesettingspage.cpp @@ -30,6 +30,7 @@ #include "cppfilesettingspage.h" #include "cpptoolsconstants.h" +#include "cpptoolsplugin.h" #include #include @@ -52,6 +53,8 @@ static const char headerSuffixKeyC[] = "HeaderSuffix"; static const char sourceSuffixKeyC[] = "SourceSuffix"; +static const char headerSearchPathsKeyC[] = "HeaderSearchPaths"; +static const char sourceSearchPathsKeyC[] = "SourceSearchPaths"; static const char licenseTemplatePathKeyC[] = "LicenseTemplate"; const char *licenseTemplateTemplate = QT_TRANSLATE_NOOP("CppTools::Internal::CppFileSettingsWidget", @@ -75,6 +78,8 @@ void CppFileSettings::toSettings(QSettings *s) const s->beginGroup(QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP)); s->setValue(QLatin1String(headerSuffixKeyC), headerSuffix); s->setValue(QLatin1String(sourceSuffixKeyC), sourceSuffix); + s->setValue(QLatin1String(headerSearchPathsKeyC), headerSearchPaths); + s->setValue(QLatin1String(sourceSearchPathsKeyC), sourceSearchPaths); s->setValue(QLatin1String(Constants::LOWERCASE_CPPFILES_KEY), lowerCaseFiles); s->setValue(QLatin1String(licenseTemplatePathKeyC), licenseTemplatePath); s->endGroup(); @@ -82,9 +87,22 @@ void CppFileSettings::toSettings(QSettings *s) const void CppFileSettings::fromSettings(QSettings *s) { + const QStringList defaultHeaderSearchPaths = QStringList() + << QLatin1String("include") + << QLatin1String("Include") + << QDir::toNativeSeparators(QLatin1String("../include")) + << QDir::toNativeSeparators(QLatin1String("../Include")); + const QStringList defaultSourceSearchPaths = QStringList() + << QDir::toNativeSeparators(QLatin1String("../src")) + << QDir::toNativeSeparators(QLatin1String("../Src")) + << QLatin1String(".."); s->beginGroup(QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP)); headerSuffix= s->value(QLatin1String(headerSuffixKeyC), QLatin1String("h")).toString(); sourceSuffix = s->value(QLatin1String(sourceSuffixKeyC), QLatin1String("cpp")).toString(); + headerSearchPaths = s->value(QLatin1String(headerSearchPathsKeyC), defaultHeaderSearchPaths) + .toStringList(); + sourceSearchPaths = s->value(QLatin1String(sourceSearchPathsKeyC), defaultSourceSearchPaths) + .toStringList(); const bool lowerCaseDefault = Constants::lowerCaseFilesDefault; lowerCaseFiles = s->value(QLatin1String(Constants::LOWERCASE_CPPFILES_KEY), QVariant(lowerCaseDefault)).toBool(); licenseTemplatePath = s->value(QLatin1String(licenseTemplatePathKeyC), QString()).toString(); @@ -102,6 +120,8 @@ bool CppFileSettings::equals(const CppFileSettings &rhs) const return lowerCaseFiles == rhs.lowerCaseFiles && headerSuffix == rhs.headerSuffix && sourceSuffix == rhs.sourceSuffix + && headerSearchPaths == rhs.headerSearchPaths + && sourceSearchPaths == rhs.sourceSearchPaths && licenseTemplatePath == rhs.licenseTemplatePath; } @@ -252,12 +272,22 @@ void CppFileSettingsWidget::setLicenseTemplatePath(const QString &lp) m_ui->licenseTemplatePathChooser->setPath(lp); } +static QStringList trimmedPaths(const QString &paths) +{ + QStringList res; + foreach (const QString &path, paths.split(QLatin1Char(','), QString::SkipEmptyParts)) + res << path.trimmed(); + return res; +} + CppFileSettings CppFileSettingsWidget::settings() const { CppFileSettings rc; rc.lowerCaseFiles = m_ui->lowerCaseFileNamesCheckBox->isChecked(); rc.headerSuffix = m_ui->headerSuffixComboBox->currentText(); rc.sourceSuffix = m_ui->sourceSuffixComboBox->currentText(); + rc.headerSearchPaths = trimmedPaths(m_ui->headerSearchPathsEdit->text()); + rc.sourceSearchPaths = trimmedPaths(m_ui->sourceSearchPathsEdit->text()); rc.licenseTemplatePath = licenseTemplatePath(); return rc; } @@ -265,8 +295,12 @@ CppFileSettings CppFileSettingsWidget::settings() const QString CppFileSettingsWidget::searchKeywords() const { QString rc; - QTextStream(&rc) << m_ui->headerSuffixLabel->text() + QTextStream(&rc) << m_ui->headersGroupBox->title() + << ' ' << m_ui->headerSuffixLabel->text() + << ' ' << m_ui->headerSearchPathsLabel->text() + << ' ' << m_ui->sourcesGroupBox->title() << ' ' << m_ui->sourceSuffixLabel->text() + << ' ' << m_ui->sourceSearchPathsLabel->text() << ' ' << m_ui->lowerCaseFileNamesCheckBox->text() << ' ' << m_ui->licenseTemplateLabel->text(); rc.remove(QLatin1Char('&')); @@ -284,6 +318,8 @@ void CppFileSettingsWidget::setSettings(const CppFileSettings &s) m_ui->lowerCaseFileNamesCheckBox->setChecked(s.lowerCaseFiles); setComboText(m_ui->headerSuffixComboBox, s.headerSuffix); setComboText(m_ui->sourceSuffixComboBox, s.sourceSuffix); + m_ui->headerSearchPathsEdit->setText(s.headerSearchPaths.join(QLatin1String(","))); + m_ui->sourceSearchPathsEdit->setText(s.sourceSearchPaths.join(QLatin1String(","))); setLicenseTemplatePath(s.licenseTemplatePath); } @@ -336,6 +372,7 @@ void CppFileSettingsPage::apply() *m_settings = newSettings; m_settings->toSettings(Core::ICore::settings()); m_settings->applySuffixesToMimeDB(); + CppToolsPlugin::clearHeaderSourceCache(); } } } diff --git a/src/plugins/cpptools/cppfilesettingspage.h b/src/plugins/cpptools/cppfilesettingspage.h index 06db6d63b3b..6fcf0dbc171 100644 --- a/src/plugins/cpptools/cppfilesettingspage.h +++ b/src/plugins/cpptools/cppfilesettingspage.h @@ -50,7 +50,9 @@ struct CppFileSettings CppFileSettings(); QString headerSuffix; + QStringList headerSearchPaths; QString sourceSuffix; + QStringList sourceSearchPaths; bool lowerCaseFiles; QString licenseTemplatePath; diff --git a/src/plugins/cpptools/cppfilesettingspage.ui b/src/plugins/cpptools/cppfilesettingspage.ui index 2b8626f05e8..5ac56063195 100644 --- a/src/plugins/cpptools/cppfilesettingspage.ui +++ b/src/plugins/cpptools/cppfilesettingspage.ui @@ -6,52 +6,148 @@ 0 0 - 291 - 142 + 547 + 305 - - - QFormLayout::ExpandingFieldsGrow - - - - - Header suffix: + + + + + + 0 + 0 + + + Headers + + + + + + &Suffix: + + + headerSuffixComboBox + + + + + + + + 0 + 0 + + + + + + + + S&earch paths: + + + headerSearchPathsEdit + + + + + + + Comma-separated list of header paths. + +Paths can be absolute or relative to the directory of the current open document. + +These paths are used in addition to current directory on Switch Header/Source. + + + + - - - - - - - Source suffix: + + + + + 0 + 0 + + + Sources + + + + + + S&uffix: + + + sourceSuffixComboBox + + + + + + + + 0 + 0 + + + + + + + + Se&arch paths: + + + sourceSearchPathsEdit + + + + + + + Comma-separated list of source paths. + +Paths can be absolute or relative to the directory of the current open document. + +These paths are used in addition to current directory on Switch Header/Source. + + + + - - - - + - Lower case file names + &Lower case file names - - - - License template: - - + + + + + + License &template: + + + licenseTemplatePathChooser + + + + + + + - - - - + Qt::Vertical @@ -65,13 +161,6 @@ - licenseTemplatePathChooser - sourceSuffixLabel - headerSuffixLabel - lowerCaseFileNamesCheckBox - headerSuffixComboBox - sourceSuffixComboBox - licenseTemplateLabel diff --git a/src/plugins/cpptools/cppheadersource_test.cpp b/src/plugins/cpptools/cppheadersource_test.cpp new file mode 100644 index 00000000000..26d833d1c40 --- /dev/null +++ b/src/plugins/cpptools/cppheadersource_test.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Orgad Shaneh . +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "cpptoolsplugin.h" +#include "cpptoolsreuse.h" + +#include + +#include +#include + +static inline QString _(const QByteArray &ba) { return QString::fromLatin1(ba, ba.size()); } + +namespace CppTools { +namespace Internal { + +void CppToolsPlugin::test_headersource() +{ + QFETCH(QString, sourceFileName); + QFETCH(QString, headerFileName); + + bool wasHeader; + Core::Internal::Tests::TestDataDir dataDir( + _(SRCDIR "/../../../tests/cppheadersource/") + _(QTest::currentDataTag())); + + const QString sourcePath = dataDir.file(sourceFileName); + const QString headerPath = dataDir.file(headerFileName); + + clearHeaderSourceCache(); + QCOMPARE(correspondingHeaderOrSource(sourcePath, &wasHeader), headerPath); + QVERIFY(!wasHeader); + clearHeaderSourceCache(); + QCOMPARE(correspondingHeaderOrSource(headerPath, &wasHeader), sourcePath); + QVERIFY(wasHeader); +} + +void CppToolsPlugin::test_headersource_data() +{ + QTest::addColumn("sourceFileName"); + QTest::addColumn("headerFileName"); + QTest::newRow("samedir") << _("foo.cpp") << _("foo.h"); + QTest::newRow("includesub") << _("foo.cpp") << _("include/foo.h"); +} + +} // namespace Internal +} // namespace CppTools diff --git a/src/plugins/cpptools/cpptools.pro b/src/plugins/cpptools/cpptools.pro index 984c6f127b2..76dc4d5c3e0 100644 --- a/src/plugins/cpptools/cpptools.pro +++ b/src/plugins/cpptools/cpptools.pro @@ -107,7 +107,8 @@ equals(TEST, 1) { cpplocatorfilter_test.cpp \ symbolsearcher_test.cpp \ cpppreprocessor_test.cpp \ - cpppreprocessertesthelper.cpp + cpppreprocessertesthelper.cpp \ + cppheadersource_test.cpp HEADERS += \ cpppreprocessertesthelper.h \ diff --git a/src/plugins/cpptools/cpptools.qbs b/src/plugins/cpptools/cpptools.qbs index 36da8f0ea5d..ae3b719e1a4 100644 --- a/src/plugins/cpptools/cpptools.qbs +++ b/src/plugins/cpptools/cpptools.qbs @@ -118,6 +118,7 @@ QtcPlugin { files: [ "cppcodegen_test.cpp", "cppcompletion_test.cpp", + "cppheadersource_test.cpp", "cppmodelmanager_test.cpp", "modelmanagertesthelper.cpp", "modelmanagertesthelper.h", "cpppointerdeclarationformatter_test.cpp", diff --git a/src/plugins/cpptools/cpptoolsplugin.cpp b/src/plugins/cpptools/cpptoolsplugin.cpp index 066bc93e07d..2d027ad091a 100644 --- a/src/plugins/cpptools/cpptoolsplugin.cpp +++ b/src/plugins/cpptools/cpptoolsplugin.cpp @@ -52,6 +52,9 @@ #include #include +#ifdef Q_OS_WIN +#include +#endif #include #include @@ -88,6 +91,21 @@ CppToolsPlugin *CppToolsPlugin::instance() return m_instance; } +void CppToolsPlugin::clearHeaderSourceCache() +{ + m_headerSourceMapping.clear(); +} + +const QStringList &CppToolsPlugin::headerSearchPaths() +{ + return m_instance->m_fileSettings->headerSearchPaths; +} + +const QStringList &CppToolsPlugin::sourceSearchPaths() +{ + return m_instance->m_fileSettings->sourceSearchPaths; +} + bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error) { Q_UNUSED(arguments) @@ -231,6 +249,14 @@ static QStringList baseNameWithAllSuffixes(const QString &baseName, const QStrin return result; } +static QStringList baseDirWithAllDirectories(const QDir &baseDir, const QStringList &directories) +{ + QStringList result; + foreach (const QString &dir, directories) + result << QDir::cleanPath(baseDir.absoluteFilePath(dir)); + return result; +} + static int commonStringLength(const QString &s1, const QString &s2) { int length = qMin(s1.length(), s2.length()); @@ -307,15 +333,28 @@ QString correspondingHeaderOrSource(const QString &fileName, bool *wasHeader) } const QDir absoluteDir = fi.absoluteDir(); + QStringList candidateDirs(absoluteDir.absolutePath()); + // If directory is not root, try matching against its siblings + const QStringList searchPaths = isHeader ? m_instance->sourceSearchPaths() + : m_instance->headerSearchPaths(); + candidateDirs += baseDirWithAllDirectories(absoluteDir, searchPaths); - // Try to find a file in the same directory first - foreach (const QString &candidateFileName, candidateFileNames) { - const QFileInfo candidateFi(absoluteDir, candidateFileName); - if (candidateFi.isFile()) { - m_headerSourceMapping[fi.absoluteFilePath()] = candidateFi.absoluteFilePath(); - if (!isHeader || !baseName.endsWith(privateHeaderSuffix)) - m_headerSourceMapping[candidateFi.absoluteFilePath()] = fi.absoluteFilePath(); - return candidateFi.absoluteFilePath(); + // Try to find a file in the same or sibling directories first + foreach (const QString &candidateDir, candidateDirs) { + foreach (const QString &candidateFileName, candidateFileNames) { + const QString candidateFilePath = candidateDir + QLatin1Char('/') + candidateFileName; +#ifdef Q_OS_WIN + const QString normalized = Utils::normalizePathName(candidateFilePath); +#else + const QString normalized = candidateFilePath; +#endif + const QFileInfo candidateFi(normalized); + if (candidateFi.isFile()) { + m_headerSourceMapping[fi.absoluteFilePath()] = candidateFi.absoluteFilePath(); + if (!isHeader || !baseName.endsWith(privateHeaderSuffix)) + m_headerSourceMapping[candidateFi.absoluteFilePath()] = fi.absoluteFilePath(); + return candidateFi.absoluteFilePath(); + } } } diff --git a/src/plugins/cpptools/cpptoolsplugin.h b/src/plugins/cpptools/cpptoolsplugin.h index 6835e365d03..152188e1ef0 100644 --- a/src/plugins/cpptools/cpptoolsplugin.h +++ b/src/plugins/cpptools/cpptoolsplugin.h @@ -60,6 +60,9 @@ public: ~CppToolsPlugin(); static CppToolsPlugin *instance(); + static const QStringList &headerSearchPaths(); + static const QStringList &sourceSearchPaths(); + static void clearHeaderSourceCache(); bool initialize(const QStringList &arguments, QString *errorMessage); void extensionsInitialized(); @@ -211,6 +214,9 @@ private slots: void test_builtinsymbolsearcher(); void test_builtinsymbolsearcher_data(); + void test_headersource_data(); + void test_headersource(); + private: void test_completion(); #endif diff --git a/tests/cppheadersource/includesub/foo.cpp b/tests/cppheadersource/includesub/foo.cpp new file mode 100644 index 00000000000..e8e1f1fa29f --- /dev/null +++ b/tests/cppheadersource/includesub/foo.cpp @@ -0,0 +1 @@ +// Comment diff --git a/tests/cppheadersource/includesub/include/foo.h b/tests/cppheadersource/includesub/include/foo.h new file mode 100644 index 00000000000..e8e1f1fa29f --- /dev/null +++ b/tests/cppheadersource/includesub/include/foo.h @@ -0,0 +1 @@ +// Comment diff --git a/tests/cppheadersource/samedir/foo.cpp b/tests/cppheadersource/samedir/foo.cpp new file mode 100644 index 00000000000..e8e1f1fa29f --- /dev/null +++ b/tests/cppheadersource/samedir/foo.cpp @@ -0,0 +1 @@ +// Comment diff --git a/tests/cppheadersource/samedir/foo.h b/tests/cppheadersource/samedir/foo.h new file mode 100644 index 00000000000..e8e1f1fa29f --- /dev/null +++ b/tests/cppheadersource/samedir/foo.h @@ -0,0 +1 @@ +// Comment