diff --git a/documentation/qthelp/qthelpconfig.cpp b/documentation/qthelp/qthelpconfig.cpp index c34b6d334e..f01703f00f 100644 --- a/documentation/qthelp/qthelpconfig.cpp +++ b/documentation/qthelp/qthelpconfig.cpp @@ -1,352 +1,352 @@ /* This file is part of KDevelop Copyright 2010 Benjamin Port Copyright 2014 Kevin Funk Copyright 2016 Andreas Cord-Landwehr 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 "qthelpconfig.h" #include #include #include #include #include #include #include #include #include "ui_qthelpconfig.h" #include "ui_qthelpconfigeditdialog.h" #include "qthelp_config_shared.h" #include "debug.h" #include "qthelpplugin.h" enum Column { NameColumn, PathColumn, IconColumn, GhnsColumn, ConfigColumn }; class QtHelpConfigEditDialog : public QDialog, public Ui_QtHelpConfigEditDialog { public: explicit QtHelpConfigEditDialog(QTreeWidgetItem* modifiedItem, QtHelpConfig* parent = nullptr, Qt::WindowFlags f = nullptr) : QDialog(parent, f) , m_modifiedItem(modifiedItem) , m_config(parent) { setupUi(this); if (modifiedItem) { setWindowTitle(i18n("Modify Entry")); } else { setWindowTitle(i18n("Add New Entry")); } qchIcon->setIcon("qtlogo"); } bool checkQtHelpFile(); void accept() override; private: QTreeWidgetItem* m_modifiedItem; QtHelpConfig* m_config; }; bool QtHelpConfigEditDialog::checkQtHelpFile() { //verify if the file is valid and if there is a name if(qchName->text().isEmpty()){ KMessageBox::error(this, i18n("Name cannot be empty.")); return false; } return m_config->checkNamespace(qchRequester->text(), m_modifiedItem); } void QtHelpConfigEditDialog::accept() { if (!checkQtHelpFile()) return; QDialog::accept(); } QtHelpConfig::QtHelpConfig(QtHelpPlugin* plugin, QWidget *parent) : KDevelop::ConfigPage(plugin, nullptr, parent) { m_configWidget = new Ui::QtHelpConfigUI; m_configWidget->setupUi(this); m_configWidget->addButton->setIcon(QIcon::fromTheme("list-add")); connect(m_configWidget->addButton, &QPushButton::clicked, this, &QtHelpConfig::add); // Table m_configWidget->qchTable->setColumnHidden(IconColumn, true); m_configWidget->qchTable->setColumnHidden(GhnsColumn, true); m_configWidget->qchTable->model()->setHeaderData(ConfigColumn, Qt::Horizontal, QVariant()); m_configWidget->qchTable->header()->setSectionsMovable(false); m_configWidget->qchTable->header()->setStretchLastSection(false); m_configWidget->qchTable->header()->setSectionResizeMode(NameColumn, QHeaderView::Stretch); m_configWidget->qchTable->header()->setSectionResizeMode(PathColumn, QHeaderView::Stretch); m_configWidget->qchTable->header()->setSectionResizeMode(ConfigColumn, QHeaderView::Fixed); // Add GHNS button KNS3::Button *knsButton = new KNS3::Button(i18nc("Allow user to get some API documentation with GHNS", "Get New Documentation"), "kdevelop-qthelp.knsrc", m_configWidget->boxQchManage); m_configWidget->tableCtrlLayout->insertWidget(1, knsButton); connect(knsButton, &KNS3::Button::dialogFinished, this, &QtHelpConfig::knsUpdate); connect(m_configWidget->loadQtDocsCheckBox, &QCheckBox::toggled, this, static_cast(&QtHelpConfig::changed)); m_configWidget->qchSearchDir->setMode(KFile::Directory); connect(m_configWidget->qchSearchDir, &KUrlRequester::textChanged, this, &QtHelpConfig::changed); // Set availability information for QtHelp m_configWidget->messageAvailabilityQtDocs->setCloseButtonVisible(false); if(plugin->isQtHelpAvailable()) { m_configWidget->messageAvailabilityQtDocs->setVisible(false); } else { m_configWidget->messageAvailabilityQtDocs->setText( i18n("The command \"qmake -query\" could not provide a path to a QtHelp file (QCH).")); m_configWidget->loadQtDocsCheckBox->setVisible(false); } reset(); } QtHelpConfig::~QtHelpConfig() { delete m_configWidget; } KDevelop::ConfigPage::ConfigPageType QtHelpConfig::configPageType() const { return KDevelop::ConfigPage::DocumentationConfigPage; } void QtHelpConfig::apply() { QStringList iconList, nameList, pathList, ghnsList; for (int i = 0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { const QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); nameList << item->text(0); pathList << item->text(1); iconList << item->text(2); ghnsList << item->text(3); } QString searchDir = m_configWidget->qchSearchDir->text(); bool loadQtDoc = m_configWidget->loadQtDocsCheckBox->isChecked(); qtHelpWriteConfig(iconList, nameList, pathList, ghnsList, searchDir, loadQtDoc); static_cast(plugin())->readConfig(); } void QtHelpConfig::reset() { m_configWidget->qchTable->clear(); QStringList iconList, nameList, pathList, ghnsList; QString searchDir; bool loadQtDoc; qtHelpReadConfig(iconList, nameList, pathList, ghnsList, searchDir, loadQtDoc); const int size = qMin(qMin(iconList.size(), nameList.size()), pathList.size()); for(int i = 0; i < size; ++i) { QString ghnsStatus = ghnsList.size()>i ? ghnsList.at(i) : "0"; addTableItem(iconList.at(i), nameList.at(i), pathList.at(i), ghnsStatus); } m_configWidget->qchSearchDir->setText(searchDir); m_configWidget->loadQtDocsCheckBox->setChecked(loadQtDoc); emit changed(); } void QtHelpConfig::defaults() { bool change = false; if(m_configWidget->qchTable->topLevelItemCount() > 0) { m_configWidget->qchTable->clear(); change = true; } if(!m_configWidget->loadQtDocsCheckBox->isChecked()){ m_configWidget->loadQtDocsCheckBox->setChecked(true); change = true; } if (change) { emit changed(); } } void QtHelpConfig::add() { QtHelpConfigEditDialog dialog(nullptr, this); if (!dialog.exec()) return; QTreeWidgetItem* item = addTableItem(dialog.qchIcon->icon(), dialog.qchName->text(), dialog.qchRequester->text(), "0"); m_configWidget->qchTable->setCurrentItem(item); emit changed(); } void QtHelpConfig::modify(QTreeWidgetItem* item) { if (!item) return; QtHelpConfigEditDialog dialog(item, this); if (item->text(GhnsColumn) != "0") { dialog.qchRequester->setText(i18n("Documentation provided by GHNS")); dialog.qchRequester->setEnabled(false); } else { dialog.qchRequester->setText(item->text(PathColumn)); dialog.qchRequester->setEnabled(true); } dialog.qchName->setText(item->text(NameColumn)); dialog.qchIcon->setIcon(item->text(IconColumn)); if (!dialog.exec()) { return; } item->setIcon(NameColumn, QIcon(dialog.qchIcon->icon())); item->setText(NameColumn, dialog.qchName->text()); item->setText(IconColumn, dialog.qchIcon->icon()); if(item->text(GhnsColumn) == "0") { item->setText(PathColumn, dialog.qchRequester->text()); } emit changed(); } bool QtHelpConfig::checkNamespace(const QString& filename, QTreeWidgetItem* modifiedItem) { QString qtHelpNamespace = QHelpEngineCore::namespaceName(filename); if (qtHelpNamespace.isEmpty()) { // Open error message (not valid Qt Compressed Help file) KMessageBox::error(this, i18n("Qt Compressed Help file is not valid.")); return false; } // verify if it's the namespace it's not already in the list for(int i=0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { const QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); if (item != modifiedItem){ if (qtHelpNamespace == QHelpEngineCore::namespaceName(item->text(PathColumn))) { // Open error message, documentation already imported KMessageBox::error(this, i18n("Documentation already imported")); return false; } } } return true; } void QtHelpConfig::remove(QTreeWidgetItem* item) { if (!item) return; delete item; emit changed(); } void QtHelpConfig::knsUpdate(KNS3::Entry::List list) { if (list.isEmpty()) return; foreach (const KNS3::Entry& e, list) { if(e.status() == KNS3::Entry::Installed) { if(e.installedFiles().size() == 1) { QString filename = e.installedFiles().at(0); if(checkNamespace(filename, nullptr)){ QTreeWidgetItem* item = addTableItem("documentation", e.name(), filename, "1"); m_configWidget->qchTable->setCurrentItem(item); } else { qCDebug(QTHELP) << "namespace error"; } } } else if(e.status() == KNS3::Entry::Deleted) { if(e.uninstalledFiles().size() == 1) { for(int i=0; i < m_configWidget->qchTable->topLevelItemCount(); i++) { QTreeWidgetItem* item = m_configWidget->qchTable->topLevelItem(i); if (e.uninstalledFiles().at(0) == item->text(PathColumn)) { delete item; break; } } } } } emit changed(); } QString QtHelpConfig::name() const { return i18n("QtHelp Documentation"); } QIcon QtHelpConfig::icon() const { return QIcon::fromTheme(QStringLiteral("qtlogo")); } QTreeWidgetItem * QtHelpConfig::addTableItem(const QString &icon, const QString &name, const QString &path, const QString &ghnsStatus) { QTreeWidgetItem *item = new QTreeWidgetItem(m_configWidget->qchTable); item->setIcon(NameColumn, QIcon::fromTheme(icon)); item->setText(NameColumn, name); item->setToolTip(NameColumn, name); item->setText(PathColumn, path); item->setToolTip(PathColumn, path); item->setText(IconColumn, icon); item->setText(GhnsColumn, ghnsStatus); QWidget *ctrlWidget = new QWidget(item->treeWidget()); ctrlWidget->setLayout(new QHBoxLayout(ctrlWidget)); QToolButton *modifyBtn = new QToolButton(item->treeWidget()); modifyBtn->setIcon(QIcon::fromTheme("document-edit")); modifyBtn->setToolTip(i18n("Modify")); connect(modifyBtn, &QPushButton::clicked, this, [=](){ modify(item); }); QToolButton *removeBtn = new QToolButton(item->treeWidget()); removeBtn->setIcon(QIcon::fromTheme("entry-delete")); removeBtn->setToolTip(i18n("Delete")); if (item->text(GhnsColumn) != "0") { // KNS3 currently does not provide API to uninstall entries // just removing the files results in wrong installed states in the KNS3 dialog // TODO: add API to KNS to remove files without UI interaction removeBtn->setEnabled(false); - removeBtn->setToolTip(tr("Please uninstall this via GHNS")); + removeBtn->setToolTip(i18n("Please uninstall this via GHNS")); } else { connect(removeBtn, &QPushButton::clicked, this, [=](){ remove(item); }); } ctrlWidget->layout()->addWidget(modifyBtn); ctrlWidget->layout()->addWidget(removeBtn); m_configWidget->qchTable->setItemWidget(item, ConfigColumn, ctrlWidget); return item; } diff --git a/languages/clang/duchain/clangproblem.cpp b/languages/clang/duchain/clangproblem.cpp index 7afbb50472..cf1c915029 100644 --- a/languages/clang/duchain/clangproblem.cpp +++ b/languages/clang/duchain/clangproblem.cpp @@ -1,268 +1,268 @@ /* * Copyright 2014 Kevin Funk * * 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 "clangproblem.h" #include #include #include "util/clangtypes.h" #include "util/clangdebug.h" #include #include #include using namespace KDevelop; namespace { IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName) { switch (severity) { case CXDiagnostic_Fatal: case CXDiagnostic_Error: return IProblem::Error; case CXDiagnostic_Warning: if (optionName.startsWith(QLatin1String("-Wunused-"))) { return IProblem::Hint; } return IProblem::Warning; break; default: return IProblem::Hint; } } /** * Clang diagnostic messages always start with a lowercase character * * @return Prettified version, starting with uppercase character */ inline QString prettyDiagnosticSpelling(const QString& str) { QString ret = str; if (ret.isEmpty()) { return {}; } ret[0] = ret[0].toUpper(); return ret; } ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic) { ClangFixits fixits; auto numFixits = clang_getDiagnosticNumFixIts(diagnostic); for (uint i = 0; i < numFixits; ++i) { CXSourceRange range; const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString(); auto location = ClangString(clang_formatDiagnostic(diagnostic, CXDiagnostic_DisplaySourceLocation)).toString(); const auto docRange = ClangRange(range).toDocumentRange(); auto doc = KDevelop::ICore::self()->documentController()->documentForUrl(docRange.document.toUrl()); const QString original = doc ? doc->text(docRange) : QString{}; fixits << ClangFixit{replacementText, docRange, QString(), original}; } return fixits; } } QDebug operator<<(QDebug debug, const ClangFixit& fixit) { debug.nospace() << "ClangFixit[" << "replacementText=" << fixit.replacementText << ", range=" << fixit.range << ", description=" << fixit.description << "]"; return debug; } ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) { const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString(); auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption); setSeverity(severity); QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString(); if (!diagnosticOption.isEmpty()) { description.append(QStringLiteral(" [%1]").arg(diagnosticOption)); } setDescription(prettyDiagnosticSpelling(description)); ClangLocation location(clang_getDiagnosticLocation(diagnostic)); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); const ClangString fileName(clang_getFileName(diagnosticFile)); DocumentRange docRange(IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), KTextEditor::Range(location, location)); const uint numRanges = clang_getDiagnosticNumRanges(diagnostic); for (uint i = 0; i < numRanges; ++i) { auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange(); if(!range.isValid()){ continue; } if (range.start() < docRange.start()) { docRange.setStart(range.start()); } if (range.end() > docRange.end()) { docRange.setEnd(range.end()); } } if (docRange.isEmpty()) { // try to find a bigger range for the given location by using the token at the given location CXFile file = nullptr; unsigned line = 0; unsigned column = 0; clang_getExpansionLocation(location, &file, &line, &column, nullptr); // just skip ahead some characters, hoping that it's sufficient to encompass // a token we can use for building the range auto nextLocation = clang_getLocation(unit, file, line, column + 100); auto rangeToTokenize = clang_getRange(location, nextLocation); const ClangTokens tokens(unit, rangeToTokenize); if (tokens.size()) { docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange()); } } setFixits(fixitsForDiagnostic(diagnostic)); setFinalLocation(docRange); setSource(IProblem::SemanticAnalysis); QVector diagnostics; auto childDiagnostics = clang_getChildDiagnostics(diagnostic); auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); for (uint j = 0; j < numChildDiagnostics; ++j) { auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit)); diagnostics << ProblemPointer(problem.data()); } setDiagnostics(diagnostics); } IAssistant::Ptr ClangProblem::solutionAssistant() const { if (allFixits().isEmpty()) { return {}; } return IAssistant::Ptr(new ClangFixitAssistant(allFixits())); } ClangFixits ClangProblem::fixits() const { return m_fixits; } void ClangProblem::setFixits(const ClangFixits& fixits) { m_fixits = fixits; } ClangFixits ClangProblem::allFixits() const { ClangFixits result; result << m_fixits; for (const IProblem::Ptr& diagnostic : diagnostics()) { const Ptr problem(dynamic_cast(diagnostic.data())); Q_ASSERT(problem); result << problem->allFixits(); } return result; } ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits) - : m_title(tr("Fix-it Hints")) + : m_title(i18n("Fix-it Hints")) , m_fixits(fixits) { } ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits) : m_title(title) , m_fixits(fixits) { } QString ClangFixitAssistant::title() const { return m_title; } void ClangFixitAssistant::createActions() { KDevelop::IAssistant::createActions(); for (const ClangFixit& fixit : m_fixits) { addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit))); } } ClangFixits ClangFixitAssistant::fixits() const { return m_fixits; } ClangFixitAction::ClangFixitAction(const ClangFixit& fixit) : m_fixit(fixit) { } QString ClangFixitAction::description() const { if (!m_fixit.description.isEmpty()) return m_fixit.description; const auto range = m_fixit.range; if (range.start() == range.end()) { return i18n("Insert \"%1\" at line: %2, column: %3", m_fixit.replacementText, range.start().line()+1, range.start().column()+1); } else if (range.start().line() == range.end().line()) { if (m_fixit.currentText.isEmpty()) { return i18n("Replace text at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } else return i18n("Replace \"%1\" with: \"%2\"", m_fixit.currentText, m_fixit.replacementText); } else { return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } } void ClangFixitAction::execute() { DocumentChangeSet changes; { DUChainReadLocker lock; DocumentChange change(m_fixit.range.document, m_fixit.range, m_fixit.currentText, m_fixit.replacementText); change.m_ignoreOldText = !m_fixit.currentText.isEmpty(); changes.addChange(change); } changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); changes.applyAllChanges(); emit executed(this); } diff --git a/languages/clang/duchain/unknowndeclarationproblem.cpp b/languages/clang/duchain/unknowndeclarationproblem.cpp index 2a68184940..90962eea83 100644 --- a/languages/clang/duchain/unknowndeclarationproblem.cpp +++ b/languages/clang/duchain/unknowndeclarationproblem.cpp @@ -1,556 +1,558 @@ /* * Copyright 2014 Jørgen Kvalsvik * Copyright 2014 Kevin Funk * * 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 "unknowndeclarationproblem.h" #include "clanghelpers.h" #include "parsesession.h" #include "../util/clangdebug.h" #include "../util/clangutils.h" #include "../util/clangtypes.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + #include #include #include #include using namespace KDevelop; namespace { /** Under some conditions, such as when looking up suggestions * for the undeclared namespace 'std' we will get an awful lot * of suggestions. This parameter limits how many suggestions * will pop up, as rarely more than a few will be relevant anyways * * Forward declaration suggestions are included in this number */ const int maxSuggestions = 5; /** * We don't want anything from the bits directory - * we'd rather prefer forwarding includes, such as */ bool isBlacklisted(const QString& path) { if (ClangHelpers::isSource(path)) return true; // Do not allow including directly from the bits directory. // Instead use one of the forwarding headers in other directories, when possible. if (path.contains( QLatin1String("bits") ) && path.contains(QLatin1String("/include/c++/"))) return true; return false; } QStringList scanIncludePaths( const QString& identifier, const QDir& dir, int maxDepth = 3 ) { if (!maxDepth) { return {}; } QStringList candidates; const auto path = dir.absolutePath(); if( isBlacklisted( path ) ) { return {}; } const QStringList nameFilters = {identifier, identifier + QLatin1String(".*")}; for (const auto& file : dir.entryList(nameFilters, QDir::Files)) { if (identifier.compare(file, Qt::CaseInsensitive) == 0 || ClangHelpers::isHeader(file)) { const QString filePath = path + QLatin1Char('/') + file; clangDebug() << "Found candidate file" << filePath; candidates.append( filePath ); } } maxDepth--; for( const auto& subdir : dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) ) candidates += scanIncludePaths( identifier, QDir{ path + QLatin1Char('/') + subdir }, maxDepth ); return candidates; } /** * Find files in dir that match the given identifier. Matches common C++ header file extensions only. */ QStringList scanIncludePaths( const QualifiedIdentifier& identifier, const KDevelop::Path::List& includes ) { const auto stripped_identifier = identifier.last().toString(); QStringList candidates; for( const auto& include : includes ) { candidates += scanIncludePaths( stripped_identifier, QDir{ include.toLocalFile() } ); } std::sort( candidates.begin(), candidates.end() ); candidates.erase( std::unique( candidates.begin(), candidates.end() ), candidates.end() ); return candidates; } /** * Determine how much path is shared between two includes. * boost/tr1/unordered_map * boost/tr1/unordered_set * have a shared path of 2 where * boost/tr1/unordered_map * boost/vector * have a shared path of 1 */ int sharedPathLevel(const QString& a, const QString& b) { int shared = -1; for(auto x = a.begin(), y = b.begin(); *x == *y && x != a.end() && y != b.end() ; ++x, ++y ) { if( *x == QDir::separator() ) { ++shared; } } return shared; } /** * Try to find a proper include position from the DUChain: * * look at existing imports (i.e. #include's) and find a fitting * file with the same/similar path to the new include file and use that * * TODO: Implement a fallback scheme */ KDevelop::DocumentRange includeDirectivePosition(const KDevelop::Path& source, const QString& includeFile) { static const QRegularExpression mocFilenameExpression(QStringLiteral("(moc_[^\\/\\\\]+\\.cpp$|\\.moc$)") ); DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << source.toLocalFile() << "Creating null range"; return KDevelop::DocumentRange::invalid(); } int line = -1; // look at existing #include statements and re-use them int currentMatchQuality = -1; for( const auto& import : top->importedParentContexts() ) { const auto importFilename = import.context(top)->url().str(); const int matchQuality = sharedPathLevel( importFilename , includeFile ); if( matchQuality < currentMatchQuality ) { continue; } const auto match = mocFilenameExpression.match(importFilename); if (match.isValid()) { clangDebug() << "moc file detected in" << source.toUrl().toDisplayString() << ":" << importFilename << "-- not using as include insertion location"; continue; } line = import.position.line + 1; currentMatchQuality = matchQuality; } if( line == -1 ) { /* Insert at the top of the document */ return {IndexedString(source.pathOrUrl()), {0, 0, 0, 0}}; } return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } KDevelop::DocumentRange forwardDeclarationPosition(const QualifiedIdentifier& identifier, const KDevelop::Path& source) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << source.toLocalFile() << "Creating null range"; return KDevelop::DocumentRange::invalid(); } if (!top->findDeclarations(identifier).isEmpty()) { // Already forward-declared return KDevelop::DocumentRange::invalid(); } int line = std::numeric_limits< int >::max(); for( const auto decl : top->localDeclarations() ) { line = std::min( line, decl->range().start.line ); } if( line == std::numeric_limits< int >::max() ) { return KDevelop::DocumentRange::invalid(); } // We want it one line above the first declaration line = std::max( line - 1, 0 ); return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } /** * Iteratively build all levels of the current scope. A (missing) type anywhere * can be arbitrarily namespaced, so we create the permutations of possible * nestings of namespaces it can currently be in, * * TODO: add detection of namespace aliases, such as 'using namespace KDevelop;' * * namespace foo { * namespace bar { * function baz() { * type var; * } * } * } * * Would give: * foo::bar::baz::type * foo::bar::type * foo::type * type */ QVector findPossibleQualifiedIdentifiers( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::CursorInRevision& cursor ) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( file.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << file.toLocalFile() << "Not creating duchain candidates"; return {}; } const auto* context = top->findContextAt( cursor ); if( !context ) { clangDebug() << "No context found at" << cursor; return {}; } QVector declarations{ identifier }; for( auto scopes = context->scopeIdentifier(); !scopes.isEmpty(); scopes.pop() ) { declarations.append( scopes + identifier ); } clangDebug() << "Possible declarations:" << declarations; return declarations; } } QStringList UnknownDeclarationProblem::findMatchingIncludeFiles(const QVector& declarations) { DUChainReadLocker lock; QStringList candidates; for (const auto decl: declarations) { // skip declarations that don't belong to us const auto& file = decl->topContext()->parsingEnvironmentFile(); if (!file || file->language() != ParseSession::languageString()) { continue; } if( dynamic_cast( decl ) ) { continue; } if( decl->isForwardDeclaration() ) { continue; } const auto filepath = decl->url().toUrl().toLocalFile(); if( !isBlacklisted( filepath ) ) { candidates << filepath; clangDebug() << "Adding" << filepath << "determined from candidate" << decl->toString(); } for( const auto importer : file->importers() ) { if( importer->imports().count() != 1 && !isBlacklisted( filepath ) ) { continue; } if( importer->topContext()->localDeclarations().count() ) { continue; } const auto filePath = importer->url().toUrl().toLocalFile(); if( isBlacklisted( filePath ) ) { continue; } /* This file is a forwarder, such as * does not actually implement the functions, but include other headers that do * we prefer this to other headers */ candidates << filePath; clangDebug() << "Adding forwarder file" << filePath << "to the result set"; } } std::sort( candidates.begin(), candidates.end() ); candidates.erase( std::unique( candidates.begin(), candidates.end() ), candidates.end() ); clangDebug() << "Candidates: " << candidates; return candidates; } namespace { /** * Takes a filepath and the include paths and determines what directive to use. */ ClangFixit directiveForFile( const QString& includefile, const KDevelop::Path::List& includepaths, const KDevelop::Path& source ) { const auto sourceFolder = source.parent(); const Path canonicalFile( QFileInfo( includefile ).canonicalFilePath() ); QString shortestDirective; bool isRelative = false; // we can include the file directly if (sourceFolder == canonicalFile.parent()) { shortestDirective = canonicalFile.lastPathSegment(); isRelative = true; } else { // find the include directive with the shortest length for( const auto& includePath : includepaths ) { QString relative = includePath.relativePath( canonicalFile ); if( relative.startsWith( QLatin1String("./") ) ) relative = relative.mid( 2 ); if( shortestDirective.isEmpty() || relative.length() < shortestDirective.length() ) { shortestDirective = relative; isRelative = includePath == sourceFolder; } } } if( shortestDirective.isEmpty() ) { // Item not found in include path return {}; } const auto range = DocumentRange(IndexedString(source.pathOrUrl()), includeDirectivePosition(source, canonicalFile.lastPathSegment())); if( !range.isValid() ) { clangDebug() << "unable to determine valid position for" << includefile << "in" << source.pathOrUrl(); return {}; } QString directive; if( isRelative ) { directive = QStringLiteral("#include \"%1\"").arg(shortestDirective); } else { directive = QStringLiteral("#include <%1>").arg(shortestDirective); } - return ClangFixit{directive + QLatin1Char('\n'), range, QObject::tr("Insert \'%1\'").arg(directive)}; + return ClangFixit{directive + QLatin1Char('\n'), range, i18n("Insert \'%1\'", directive)}; } KDevelop::Path::List includePaths( const KDevelop::Path& file ) { // Find project's custom include paths const auto source = file.toLocalFile(); const auto item = ICore::self()->projectController()->projectModel()->itemForPath( KDevelop::IndexedString( source ) ); return IDefinesAndIncludesManager::manager()->includes(item); } /** * Return a list of header files viable for inclusions. All elements will be unique */ QStringList includeFiles(const QualifiedIdentifier& identifier, const QVector declarations, const KDevelop::Path& file) { const auto includes = includePaths( file ); if( includes.isEmpty() ) { clangDebug() << "Include path is empty"; return {}; } const auto candidates = UnknownDeclarationProblem::findMatchingIncludeFiles(declarations); if( !candidates.isEmpty() ) { // If we find a candidate from the duchain we don't bother scanning the include paths return candidates; } return scanIncludePaths(identifier, includes); } /** * Construct viable forward declarations for the type name. */ ClangFixits forwardDeclarations(const QVector& matchingDeclarations, const Path& source) { DUChainReadLocker lock; ClangFixits fixits; for (const auto decl : matchingDeclarations) { const auto qid = decl->qualifiedIdentifier(); if (qid.count() > 1) { // TODO: Currently we're not able to determine what is namespaces, class names etc // and makes a suitable forward declaration, so just suggest "vanilla" declarations. continue; } const auto range = forwardDeclarationPosition(qid, source); if (!range.isValid()) { continue; // do not know where to insert } if (const auto classDecl = dynamic_cast(decl)) { const auto name = qid.last().toString(); switch (classDecl->classType()) { case ClassDeclarationData::Class: fixits += { QLatin1String("class ") + name + QLatin1String(";\n"), range, - QObject::tr("Forward declare as 'class'") + i18n("Forward declare as 'class'") }; break; case ClassDeclarationData::Struct: fixits += { QLatin1String("struct ") + name + QLatin1String(";\n"), range, - QObject::tr("Forward declare as 'struct'") + i18n("Forward declare as 'struct'") }; break; default: break; } } } return fixits; } /** * Search the persistent symbol table for matching declarations for identifiers @p identifiers */ QVector findMatchingDeclarations(const QVector& identifiers) { DUChainReadLocker lock; QVector matchingDeclarations; matchingDeclarations.reserve(identifiers.size()); for (const auto& declaration : identifiers) { clangDebug() << "Considering candidate declaration" << declaration; const IndexedDeclaration* declarations; uint declarationCount; PersistentSymbolTable::self().declarations( declaration , declarationCount, declarations ); for (uint i = 0; i < declarationCount; ++i) { // Skip if the declaration is invalid or if it is an alias declaration - // we want the actual declaration (and its file) if (auto decl = declarations[i].declaration()) { matchingDeclarations << decl; } } } return matchingDeclarations; } ClangFixits fixUnknownDeclaration( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::DocumentRange& docrange ) { ClangFixits fixits; const CursorInRevision cursor{docrange.start().line(), docrange.start().column()}; const auto possibleIdentifiers = findPossibleQualifiedIdentifiers(identifier, file, cursor); const auto matchingDeclarations = findMatchingDeclarations(possibleIdentifiers); if (ClangSettingsManager::self()->assistantsSettings().forwardDeclare) { for (const auto& fixit : forwardDeclarations(matchingDeclarations, file)) { fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; } } } const auto includefiles = includeFiles(identifier, matchingDeclarations, file); if (includefiles.isEmpty()) { // return early as the computation of the include paths is quite expensive return fixits; } const auto includepaths = includePaths( file ); clangDebug() << "found include paths for" << file << ":" << includepaths; /* create fixits for candidates */ for( const auto& includeFile : includefiles ) { const auto fixit = directiveForFile( includeFile, includepaths, file /* UP */ ); if (!fixit.range.isValid()) { clangDebug() << "unable to create directive for" << includeFile << "in" << file.toLocalFile(); continue; } fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; } } return fixits; } QString symbolFromDiagnosticSpelling(const QString& str) { /* in all error messages the symbol is in in the first pair of quotes */ const auto split = str.split( QLatin1Char('\'') ); auto symbol = split.value( 1 ); if( str.startsWith( QLatin1String("No member named") ) ) { symbol = split.value( 3 ) + QLatin1String("::") + split.value( 1 ); } return symbol; } } UnknownDeclarationProblem::UnknownDeclarationProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) : ClangProblem(diagnostic, unit) { setSymbol(QualifiedIdentifier(symbolFromDiagnosticSpelling(description()))); } void UnknownDeclarationProblem::setSymbol(const QualifiedIdentifier& identifier) { m_identifier = identifier; } IAssistant::Ptr UnknownDeclarationProblem::solutionAssistant() const { const Path path(finalLocation().document.str()); const auto fixits = allFixits() + fixUnknownDeclaration(m_identifier, path, finalLocation()); return IAssistant::Ptr(new ClangFixitAssistant(fixits)); } diff --git a/languages/clang/tests/test_assistants.cpp b/languages/clang/tests/test_assistants.cpp index 2bf5fb5ec9..64c2610bd2 100644 --- a/languages/clang/tests/test_assistants.cpp +++ b/languages/clang/tests/test_assistants.cpp @@ -1,791 +1,793 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 2014 David Stevens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "test_assistants.h" #include "codegen/clangrefactoring.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 using namespace KDevelop; using namespace KTextEditor; QTEST_MAIN(TestAssistants) ForegroundLock *globalTestLock = nullptr; StaticAssistantsManager *staticAssistantsManager() { return Core::self()->languageController()->staticAssistantsManager(); } void TestAssistants::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral( "*.debug=false\n" "default.debug=true\n" "kdevelop.plugins.clang.debug=true\n" )); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport"), QStringLiteral("kdevproblemreporter")}); TestCore::initialize(); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); Core::self()->sourceFormatterController()->disableSourceFormatting(true); CodeRepresentation::setDiskChangesForbidden(true); globalTestLock = new ForegroundLock; } void TestAssistants::cleanupTestCase() { Core::self()->cleanup(); delete globalTestLock; globalTestLock = nullptr; } static QUrl createFile(const QString& fileContents, QString extension, int id) { static QTemporaryDir tempDirA; Q_ASSERT(tempDirA.isValid()); static QDir dirA(tempDirA.path()); QFile file(dirA.filePath(QString::number(id) + extension)); file.open(QIODevice::WriteOnly | QIODevice::Text); file.write(fileContents.toUtf8()); file.close(); return QUrl::fromLocalFile(file.fileName()); } class Testbed { public: enum TestDoc { HeaderDoc, CppDoc }; enum IncludeBehavior { NoAutoInclude, AutoInclude, }; Testbed(const QString& headerContents, const QString& cppContents, IncludeBehavior include = AutoInclude) : m_includeBehavior(include) { static int i = 0; int id = i; ++i; m_headerDocument.url = createFile(headerContents,".h",id); m_headerDocument.textDoc = openDocument(m_headerDocument.url); QString preamble; if (include == AutoInclude) preamble = QStringLiteral("#include \"%1\"\n").arg(m_headerDocument.url.toLocalFile()); m_cppDocument.url = createFile(preamble + cppContents,".cpp",id); m_cppDocument.textDoc = openDocument(m_cppDocument.url); } ~Testbed() { Core::self()->documentController()->documentForUrl(m_cppDocument.url)->textDocument(); Core::self()->documentController()->documentForUrl(m_cppDocument.url)->close(KDevelop::IDocument::Discard); Core::self()->documentController()->documentForUrl(m_headerDocument.url)->close(KDevelop::IDocument::Discard); } void changeDocument(TestDoc which, Range where, const QString& what, bool waitForUpdate = false) { TestDocument document; if (which == CppDoc) { document = m_cppDocument; if (m_includeBehavior == AutoInclude) { where = Range(where.start().line() + 1, where.start().column(), where.end().line() + 1, where.end().column()); //The include adds a line } } else { document = m_headerDocument; } // we must activate the document, otherwise we cannot find the correct active view auto kdevdoc = ICore::self()->documentController()->documentForUrl(document.url); QVERIFY(kdevdoc); ICore::self()->documentController()->activateDocument(kdevdoc); auto view = ICore::self()->documentController()->activeTextDocumentView(); QCOMPARE(view->document(), document.textDoc); view->setSelection(where); view->removeSelectionText(); view->setCursorPosition(where.start()); view->insertText(what); QCoreApplication::processEvents(); if (waitForUpdate) { DUChain::self()->waitForUpdate(IndexedString(document.url), KDevelop::TopDUContext::AllDeclarationsAndContexts); } } QString documentText(TestDoc which) { if (which == CppDoc) { //The CPP document text shouldn't include the autogenerated include line QString text = m_cppDocument.textDoc->text(); return m_includeBehavior == AutoInclude ? text.mid(text.indexOf("\n") + 1) : text; } else return m_headerDocument.textDoc->text(); } QString includeFileName() const { return m_headerDocument.url.toLocalFile(); } KTextEditor::Document *document(TestDoc which) const { return Core::self()->documentController()->documentForUrl( which == CppDoc ? m_cppDocument.url : m_headerDocument.url)->textDocument(); } private: struct TestDocument { QUrl url; Document *textDoc; }; Document* openDocument(const QUrl& url) { Core::self()->documentController()->openDocument(url); DUChain::self()->waitForUpdate(IndexedString(url), KDevelop::TopDUContext::AllDeclarationsAndContexts); return Core::self()->documentController()->documentForUrl(url)->textDocument(); } IncludeBehavior m_includeBehavior; TestDocument m_headerDocument; TestDocument m_cppDocument; }; /** * A StateChange describes an insertion/deletion/replacement and the expected result **/ struct StateChange { StateChange(){}; StateChange(Testbed::TestDoc document, const Range& range, const QString& newText, const QString& result) : document(document) , range(range) , newText(newText) , result(result) { } Testbed::TestDoc document; Range range; QString newText; QString result; }; Q_DECLARE_METATYPE(StateChange) Q_DECLARE_METATYPE(QList) void TestAssistants::testRenameAssistant_data() { QTest::addColumn("fileContents"); QTest::addColumn("oldDeclarationName"); QTest::addColumn >("stateChanges"); QTest::addColumn("finalFileContents"); QTest::newRow("Prepend Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << QList{ StateChange(Testbed::CppDoc, Range(0,12,0,12), "u", "ui"), StateChange(Testbed::CppDoc, Range(0,13,0,13), "z", "uzi"), } << "int foo(int uzi)\n { uzi = 0; return uzi; }"; QTest::newRow("Append Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,13), "d", "id")) << "int foo(int id)\n { id = 0; return id; }"; QTest::newRow("Replace Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,13), "u", "u")) << "int foo(int u)\n { u = 0; return u; }"; QTest::newRow("Paste Replace") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,15), "abcdefg", "abcdefg")) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; QTest::newRow("Paste Insert") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "cdef", "abcdefg")) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; QTest::newRow("Letter-by-Letter Prepend") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,12), "a", "ai") << StateChange(Testbed::CppDoc, Range(0,13,0,13), "b", "abi") << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abci") ) << "int foo(int abci)\n { abci = 0; return abci; }"; QTest::newRow("Letter-by-Letter Insert") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abcg") << StateChange(Testbed::CppDoc, Range(0,15,0,15), "d", "abcdg") << StateChange(Testbed::CppDoc, Range(0,16,0,16), "e", "abcdeg") << StateChange(Testbed::CppDoc, Range(0,17,0,17), "f", "abcdefg") ) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; } ProblemPointer findStaticAssistantProblem(const QVector& problems) { const auto renameProblemIt = std::find_if(problems.cbegin(), problems.cend(), [](const ProblemPointer& p) { return dynamic_cast(p.constData()); }); if (renameProblemIt != problems.cend()) return *renameProblemIt; return {}; } void TestAssistants::testRenameAssistant() { QFETCH(QString, fileContents); Testbed testbed("", fileContents); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); QExplicitlySharedDataPointer assistant; QFETCH(QString, oldDeclarationName); QFETCH(QList, stateChanges); foreach(StateChange stateChange, stateChanges) { testbed.changeDocument(Testbed::CppDoc, stateChange.range, stateChange.newText, true); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); if (problem) assistant = problem->solutionAssistant(); if (stateChange.result.isEmpty()) { QVERIFY(!assistant || !assistant->actions().size()); } else { qWarning() << assistant.data() << stateChange.result; QVERIFY(assistant && assistant->actions().size()); RenameAction *r = qobject_cast(assistant->actions().first().data()); QCOMPARE(r->oldDeclarationName(), oldDeclarationName); QCOMPARE(r->newDeclarationName(), stateChange.result); } } if (assistant && assistant->actions().size()) { assistant->actions().first()->execute(); } QFETCH(QString, finalFileContents); QCOMPARE(testbed.documentText(Testbed::CppDoc), finalFileContents); } void TestAssistants::testRenameAssistantUndoRename() { Testbed testbed("", "int foo(int i)\n { i = 0; return i; }"); testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,13), "d", true); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); auto firstProblem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); QVERIFY(firstProblem); auto assistant = firstProblem->solutionAssistant(); QVERIFY(assistant); QVERIFY(assistant->actions().size() > 0); RenameAction *r = qobject_cast(assistant->actions().first().data()); qWarning() << topCtx->problems() << assistant->actions().first().data() << assistant->actions().size(); QVERIFY(r); // now rename the variable back to its original identifier testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,14), ""); // there should be no assistant anymore QVERIFY(!assistant || assistant->actions().isEmpty()); } const QString SHOULD_ASSIST = "SHOULD_ASSIST"; //An assistant will be visible const QString NO_ASSIST = "NO_ASSIST"; //No assistant visible void TestAssistants::testSignatureAssistant_data() { QTest::addColumn("headerContents"); QTest::addColumn("cppContents"); QTest::addColumn >("stateChanges"); QTest::addColumn("finalHeaderContents"); QTest::addColumn("finalCppContents"); QTest::newRow("change_argument_type") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::HeaderDoc, Range(1,8,1,11), "char", SHOULD_ASSIST)) << "class Foo {\nint bar(char a, char* b, int c = 10); \n};" << "int Foo::bar(char a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("prepend_arg_header") << "class Foo { void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 21, 0, 21), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, int i); };" << "void Foo::bar(char c, int i)\n{}"; QTest::newRow("prepend_arg_cpp") << "class Foo { void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, int i); };" << "void Foo::bar(char c, int i)\n{}"; QTest::newRow("change_default_parameter") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::HeaderDoc, Range(1,29,1,34), "", NO_ASSIST)) << "class Foo {\nint bar(int a, char* b, int c); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("change_function_type") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::CppDoc, Range(0,0,0,3), "char", SHOULD_ASSIST)) << "class Foo {\nchar bar(int a, char* b, int c = 10); \n};" << "char Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("swap_args_definition_side") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,28), "char* b, int a,", SHOULD_ASSIST)) << "class Foo {\nint bar(char* b, int a, int c = 10); \n};" << "int Foo::bar(char* b, int a, int c)\n{ a = c; b = new char; return a + *b; }"; // see https://bugs.kde.org/show_bug.cgi?id=299393 // actually related to the whitespaces in the header... QTest::newRow("change_function_constness") << "class Foo {\nvoid bar(const Foo&) const;\n};" << "void Foo::bar(const Foo&) const\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0,25,0,31), "", SHOULD_ASSIST)) << "class Foo {\nvoid bar(const Foo&);\n};" << "void Foo::bar(const Foo&)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=356179 QTest::newRow("keep_static_cpp") << "class Foo { static void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 19, 0, 19), ", char c", SHOULD_ASSIST)) << "class Foo { static void bar(int i, char c); };" << "void Foo::bar(int i, char c)\n{}"; QTest::newRow("keep_static_header") << "class Foo { static void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 33, 0, 33), ", char c", SHOULD_ASSIST)) << "class Foo { static void bar(int i, char c); };" << "void Foo::bar(int i, char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=356178 QTest::newRow("keep_default_args_cpp_before") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, bool b, int i = 0); };" << "void Foo::bar(char c, bool b, int i)\n{}"; QTest::newRow("keep_default_args_cpp_after") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 27, 0, 27), ", char c", SHOULD_ASSIST)) << "class Foo { void bar(bool b, int i = 0, char c = {} /* TODO */); };" << "void Foo::bar(bool b, int i, char c)\n{}"; QTest::newRow("keep_default_args_header_before") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 29, 0, 29), "char c = 'A', ", SHOULD_ASSIST)) << "class Foo { void bar(bool b, char c = 'A', int i = 0); };" << "void Foo::bar(bool b, char c, int i)\n{}"; QTest::newRow("keep_default_args_header_after") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 38, 0, 38), ", char c = 'A'", SHOULD_ASSIST)) << "class Foo { void bar(bool b, int i = 0, char c = 'A'); };" << "void Foo::bar(bool b, int i, char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=355356 QTest::newRow("no_retval_on_ctor") << "class Foo { Foo(); };" << "Foo::Foo()\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 16, 0, 16), "char c", SHOULD_ASSIST)) << "class Foo { Foo(char c); };" << "Foo::Foo(char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=298511 QTest::newRow("change_return_type_header") << "struct Foo { int bar(); };" << "int Foo::bar()\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 13, 0, 16), "char", SHOULD_ASSIST)) << "struct Foo { char bar(); };" << "char Foo::bar()\n{}"; QTest::newRow("change_return_type_impl") << "struct Foo { int bar(); };" << "int Foo::bar()\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 0, 0, 3), "char", SHOULD_ASSIST)) << "struct Foo { char bar(); };" << "char Foo::bar()\n{}"; } void TestAssistants::testSignatureAssistant() { QFETCH(QString, headerContents); QFETCH(QString, cppContents); Testbed testbed(headerContents, cppContents); QExplicitlySharedDataPointer assistant; QFETCH(QList, stateChanges); foreach (StateChange stateChange, stateChanges) { testbed.changeDocument(stateChange.document, stateChange.range, stateChange.newText, true); const auto document = testbed.document(stateChange.document); QVERIFY(document); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); if (problem) { assistant = problem->solutionAssistant(); } if (stateChange.result == SHOULD_ASSIST) { #if CINDEX_VERSION_MINOR < 35 QEXPECT_FAIL("change_function_type", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't parse the code...", Abort); QEXPECT_FAIL("change_return_type_impl", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't include the function's AST and thus we never get updated about the new return type...", Abort); #endif QVERIFY(assistant && !assistant->actions().isEmpty()); } else { QVERIFY(!assistant || assistant->actions().isEmpty()); } } if (assistant && !assistant->actions().isEmpty()) assistant->actions().first()->execute(); QFETCH(QString, finalHeaderContents); QFETCH(QString, finalCppContents); QCOMPARE(testbed.documentText(Testbed::HeaderDoc), finalHeaderContents); QCOMPARE(testbed.documentText(Testbed::CppDoc), finalCppContents); } enum UnknownDeclarationAction { NoUnknownDeclarationAction = 0x0, ForwardDecls = 0x1, MissingInclude = 0x2 }; Q_DECLARE_FLAGS(UnknownDeclarationActions, UnknownDeclarationAction) Q_DECLARE_METATYPE(UnknownDeclarationActions) void TestAssistants::testUnknownDeclarationAssistant_data() { QTest::addColumn("headerContents"); QTest::addColumn("globalText"); QTest::addColumn("functionText"); QTest::addColumn("actions"); QTest::newRow("unincluded_struct") << "struct test{};" << "" << "test" << UnknownDeclarationActions(ForwardDecls | MissingInclude); QTest::newRow("forward_declared_struct") << "struct test{};" << "struct test;" << "test *f; f->" << UnknownDeclarationActions(MissingInclude); QTest::newRow("unknown_struct") << "" << "" << "test" << UnknownDeclarationActions(); QTest::newRow("not a class type") << "void test();" << "" << "test" << UnknownDeclarationActions(); } void TestAssistants::testUnknownDeclarationAssistant() { QFETCH(QString, headerContents); QFETCH(QString, globalText); QFETCH(QString, functionText); QFETCH(UnknownDeclarationActions, actions); static const auto cppContents = QStringLiteral("%1\nvoid f_u_n_c_t_i_o_n() {\n}"); Testbed testbed(headerContents, cppContents.arg(globalText), Testbed::NoAutoInclude); const auto document = testbed.document(Testbed::CppDoc); QVERIFY(document); const int line = document->lines() - 1; testbed.changeDocument(Testbed::CppDoc, Range(line, 0, line, 0), functionText, true); DUChainReadLocker lock; auto topCtx = DUChain::self()->chainForDocument(document->url()); QVERIFY(topCtx); const auto problems = topCtx->problems(); if (actions == NoUnknownDeclarationAction) { QVERIFY(!problems.isEmpty()); return; } auto firstProblem = problems.first(); auto assistant = firstProblem->solutionAssistant(); QVERIFY(assistant); const auto assistantActions = assistant->actions(); QStringList actionDescriptions; for (auto action: assistantActions) { actionDescriptions << action->description(); } { const bool hasForwardDecls = - actionDescriptions.contains(QObject::tr("Forward declare as 'struct'")) || - actionDescriptions.contains(QObject::tr("Forward declare as 'class'")); + actionDescriptions.contains(i18n("Forward declare as 'struct'")) || + actionDescriptions.contains(i18n("Forward declare as 'class'")); QCOMPARE(hasForwardDecls, static_cast(actions & ForwardDecls)); } { auto fileName = testbed.includeFileName(); fileName = fileName.mid(fileName.lastIndexOf('/') + 1); - const auto description = QObject::tr("Insert \'%1\'") - .arg(QStringLiteral("#include \"%1\"").arg(fileName)); + const auto directive = QStringLiteral("#include \"%1\"").arg(fileName); + const auto description = i18n("Insert \'%1\'", directive); const bool hasMissingInclude = actionDescriptions.contains(description); QCOMPARE(hasMissingInclude, static_cast(actions & MissingInclude)); } } void TestAssistants::testMoveIntoSource() { QFETCH(QString, origHeader); QFETCH(QString, origImpl); QFETCH(QString, newHeader); QFETCH(QString, newImpl); QFETCH(QualifiedIdentifier, id); TestFile header(origHeader, "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" + origImpl, "cpp", &header); { TopDUContext* headerCtx = nullptr; { DUChainReadLocker lock; headerCtx = DUChain::self()->chainForDocument(header.url()); } // Here is a problem: when launching tests one by one, we can reuse the same tmp file for headers. // But because of document chain for header wasn't unloaded properly in previous run we reuse it here // Therefore when using headerCtx->findDeclarations below we find declarations from the previous launch -> tests fail if (headerCtx) { // TODO: Investigate why this chain doesn't get updated when parsing source file DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(headerCtx); } } impl.parse(KDevelop::TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl.waitForParsed()); IndexedDeclaration declaration; { DUChainReadLocker lock; auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); auto decls = headerCtx->findDeclarations(id); Q_ASSERT(!decls.isEmpty()); declaration = IndexedDeclaration(decls.first()); QVERIFY(declaration.isValid()); } CodeRepresentation::setDiskChangesForbidden(false); ClangRefactoring refactoring; QCOMPARE(refactoring.moveIntoSource(declaration), QString()); CodeRepresentation::setDiskChangesForbidden(true); QCOMPARE(header.fileContents(), newHeader); QVERIFY(impl.fileContents().endsWith(newImpl)); } void TestAssistants::testMoveIntoSource_data() { QTest::addColumn("origHeader"); QTest::addColumn("origImpl"); QTest::addColumn("newHeader"); QTest::addColumn("newImpl"); QTest::addColumn("id"); const QualifiedIdentifier fooId("foo"); QTest::newRow("globalfunction") << QString("int foo()\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo();\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("staticfunction") << QString("static int foo()\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("static int foo();\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("funcsameline") << QString("int foo() {\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo();\n") << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("func-comment") << QString("int foo()\n/* foobar */ {\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo()\n/* foobar */;\n") << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") << fooId; QTest::newRow("func-comment2") << QString("int foo()\n/*asdf*/\n{\n int i = 0;\n return 0;\n}\n") << QString() << QString("int foo()\n/*asdf*/;\n") << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") << fooId; const QualifiedIdentifier aFooId("a::foo"); QTest::newRow("class-method") << QString("class a {\n int foo(){\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo();\n};\n") << QString("\nint a::foo() {\n return 0;\n }\n") << aFooId; QTest::newRow("class-method-const") << QString("class a {\n int foo() const\n {\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo() const;\n};\n") << QString("\nint a::foo() const\n {\n return 0;\n }\n") << aFooId; QTest::newRow("class-method-const-sameline") << QString("class a {\n int foo() const{\n return 0;\n }\n};\n") << QString() << QString("class a {\n int foo() const;\n};\n") << QString("\nint a::foo() const {\n return 0;\n }\n") << aFooId; QTest::newRow("elaborated-type") << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const{\nreturn 0;\n}\n};\n") << QString() << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const;\n};\n") << QString("\nint a::foo(const NS::C c) const {\nreturn 0;\n}\n") << aFooId; QTest::newRow("add-into-namespace") << QString("namespace NS{class a {\nint foo() const {\nreturn 0;\n}\n};\n}") << QString("namespace NS{\n}") << QString("namespace NS{class a {\nint foo() const;\n};\n}") << QString("namespace NS{\n\nint a::foo() const {\nreturn 0;\n}\n}") << QualifiedIdentifier("NS::a::foo"); QTest::newRow("class-template-parameter") << QString(R"( namespace first { template class Test{}; namespace second { template class List; } class MoveIntoSource { public: void f(const second::List>*>& param){} };} )") << QString("") << QString(R"( namespace first { template class Test{}; namespace second { template class List; } class MoveIntoSource { public: void f(const second::List>*>& param); };} )") << QString("namespace first {\nvoid MoveIntoSource::f(const first::second::List< const volatile first::Test< first::second::List< int* > >* >& param) {}}\n\n") << QualifiedIdentifier("first::MoveIntoSource::f"); QTest::newRow("move-unexposed-type") << QString("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i){}") << QString("") << QString("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i);") << QString("void move(std::string i) {}\n") << QualifiedIdentifier("move"); QTest::newRow("move-constructor") << QString("class Class{Class(){}\n};") << QString("") << QString("class Class{Class();\n};") << QString("Class::Class() {}\n") << QualifiedIdentifier("Class::Class"); } diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp index b2b376ed55..f3d19b8db9 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp +++ b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp @@ -1,307 +1,307 @@ /************************************************************************ * * * Copyright 2010 Andreas Pakulat * * Copyright 2014 Sergey Kalinichev * * * * 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, see . * ************************************************************************/ #include "projectpathswidget.h" #include #include #include #include #include #include #include #include #include "../compilerprovider/compilerprovider.h" #include "../compilerprovider/settingsmanager.h" #include "ui_projectpathswidget.h" #include "ui_batchedit.h" #include "projectpathsmodel.h" #include "debugarea.h" using namespace KDevelop; namespace { enum PageType { IncludesPage, DefinesPage, ParserArgumentsPage }; } ProjectPathsWidget::ProjectPathsWidget( QWidget* parent ) : QWidget(parent), ui(new Ui::ProjectPathsWidget), pathsModel(new ProjectPathsModel(this)) { ui->setupUi( this ); ui->addPath->setIcon(QIcon::fromTheme("list-add")); ui->removePath->setIcon(QIcon::fromTheme("list-remove")); // hack taken from kurlrequester, make the buttons a bit less in height so they better match the url-requester ui->addPath->setFixedHeight( ui->projectPaths->sizeHint().height() ); ui->removePath->setFixedHeight( ui->projectPaths->sizeHint().height() ); connect( ui->addPath, &QPushButton::clicked, this, &ProjectPathsWidget::addProjectPath ); connect( ui->removePath, &QPushButton::clicked, this, &ProjectPathsWidget::deleteProjectPath ); connect( ui->batchEdit, &QPushButton::clicked, this, &ProjectPathsWidget::batchEdit ); ui->projectPaths->setModel( pathsModel ); connect( ui->projectPaths, static_cast(&KComboBox::currentIndexChanged), this, &ProjectPathsWidget::projectPathSelected ); connect( pathsModel, &ProjectPathsModel::dataChanged, this, &ProjectPathsWidget::changed ); connect( pathsModel, &ProjectPathsModel::rowsInserted, this, &ProjectPathsWidget::changed ); connect( pathsModel, &ProjectPathsModel::rowsRemoved, this, &ProjectPathsWidget::changed ); connect( ui->compiler, static_cast(&QComboBox::activated), this, &ProjectPathsWidget::changed ); connect( ui->compiler, static_cast(&QComboBox::activated), this, &ProjectPathsWidget::changeCompilerForPath ); connect( ui->includesWidget, static_cast(&IncludesWidget::includesChanged), this, &ProjectPathsWidget::includesChanged ); connect( ui->definesWidget, static_cast(&DefinesWidget::definesChanged), this, &ProjectPathsWidget::definesChanged ); connect(ui->languageParameters, &QTabWidget::currentChanged, this, &ProjectPathsWidget::tabChanged); connect(ui->parserWidget, &ParserWidget::changed, this, &ProjectPathsWidget::parserArgumentsChanged); tabChanged(IncludesPage); } QVector ProjectPathsWidget::paths() const { return pathsModel->paths(); } void ProjectPathsWidget::setPaths( const QVector& paths ) { bool b = blockSignals( true ); clear(); pathsModel->setPaths( paths ); blockSignals( b ); ui->projectPaths->setCurrentIndex(0); // at least a project root item is present ui->languageParameters->setCurrentIndex(0); // Set compilers ui->compiler->clear(); auto settings = SettingsManager::globalInstance(); auto compilers = settings->provider()->compilers(); for (int i = 0 ; i < compilers.count(); ++i) { Q_ASSERT(compilers[i]); if (!compilers[i]) { continue; } ui->compiler->addItem(compilers[i]->name()); QVariant val; val.setValue(compilers[i]); ui->compiler->setItemData(i, val); } projectPathSelected(0); updateEnablements(); } void ProjectPathsWidget::definesChanged( const Defines& defines ) { definesAndIncludesDebug() << "defines changed"; updatePathsModel( QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole ); } void ProjectPathsWidget::includesChanged( const QStringList& includes ) { definesAndIncludesDebug() << "includes changed"; updatePathsModel( includes, ProjectPathsModel::IncludesDataRole ); } void ProjectPathsWidget::parserArgumentsChanged() { updatePathsModel(QVariant::fromValue(ui->parserWidget->parserArguments()), ProjectPathsModel::ParserArgumentsRole); } void ProjectPathsWidget::updatePathsModel(const QVariant& newData, int role) { QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0, QModelIndex() ); if( idx.isValid() ) { bool b = pathsModel->setData( idx, newData, role ); if( b ) { emit changed(); } } } void ProjectPathsWidget::projectPathSelected( int index ) { if( index < 0 && pathsModel->rowCount() > 0 ) { index = 0; } Q_ASSERT(index >= 0); const QModelIndex midx = pathsModel->index( index, 0 ); ui->includesWidget->setIncludes( pathsModel->data( midx, ProjectPathsModel::IncludesDataRole ).toStringList() ); ui->definesWidget->setDefines( pathsModel->data( midx, ProjectPathsModel::DefinesDataRole ).value() ); Q_ASSERT(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value()); ui->compiler->setCurrentText(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value()->name()); ui->parserWidget->setParserArguments(pathsModel->data(midx, ProjectPathsModel::ParserArgumentsRole).value()); updateEnablements(); } void ProjectPathsWidget::clear() { bool sigDisabled = ui->projectPaths->blockSignals( true ); pathsModel->setPaths({}); ui->includesWidget->clear(); ui->definesWidget->clear(); updateEnablements(); ui->projectPaths->blockSignals( sigDisabled ); } void ProjectPathsWidget::addProjectPath() { const QUrl directory = pathsModel->data(pathsModel->index(0, 0), ProjectPathsModel::FullUrlDataRole).value(); - QFileDialog dlg(this, tr("Select Project Path"), directory.toLocalFile()); + QFileDialog dlg(this, i18n("Select Project Path"), directory.toLocalFile()); dlg.setFileMode(QFileDialog::Directory); dlg.setOption(QFileDialog::ShowDirsOnly); dlg.exec(); pathsModel->addPath(dlg.selectedUrls().value(0)); ui->projectPaths->setCurrentIndex(pathsModel->rowCount() - 1); updateEnablements(); } void ProjectPathsWidget::deleteProjectPath() { const QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0 ); if( KMessageBox::questionYesNo( this, i18n("Are you sure you want to remove the configuration for the path '%1'?", pathsModel->data( idx, Qt::DisplayRole ).toString() ), "Remove Path Configuration" ) == KMessageBox::Yes ) { pathsModel->removeRows( ui->projectPaths->currentIndex(), 1 ); } updateEnablements(); } void ProjectPathsWidget::setProject(KDevelop::IProject* w_project) { pathsModel->setProject( w_project ); ui->includesWidget->setProject( w_project ); } void ProjectPathsWidget::updateEnablements() { // Disable removal of the project root entry which is always first in the list ui->removePath->setEnabled( ui->projectPaths->currentIndex() > 0 ); } void ProjectPathsWidget::batchEdit() { Ui::BatchEdit be; QDialog dialog(this); be.setupUi(&dialog); const int index = qMax(ui->projectPaths->currentIndex(), 0); const QModelIndex midx = pathsModel->index(index, 0); if (!midx.isValid()) { return; } bool includesTab = ui->languageParameters->currentIndex() == 0; if (includesTab) { auto includes = pathsModel->data(midx, ProjectPathsModel::IncludesDataRole).toStringList(); be.textEdit->setPlainText(includes.join("\n")); dialog.setWindowTitle(i18n("Edit include directories/files")); } else { auto defines = pathsModel->data(midx, ProjectPathsModel::DefinesDataRole).value(); for (auto it = defines.constBegin(); it != defines.constEnd(); it++) { be.textEdit->append(it.key() + "=" + it.value()); } dialog.setWindowTitle(i18n("Edit defined macros")); } if (dialog.exec() != QDialog::Accepted) { return; } if (includesTab) { auto includes = be.textEdit->toPlainText().split('\n', QString::SkipEmptyParts); for (auto& s : includes) { s = s.trimmed(); } pathsModel->setData(midx, includes, ProjectPathsModel::IncludesDataRole); } else { auto list = be.textEdit->toPlainText().split('\n', QString::SkipEmptyParts); Defines defines; for (auto& d : list) { //This matches: a=b, a=, a QRegExp r("^([^=]+)(=(.*))?$"); if (!r.exactMatch(d)) { continue; } defines[r.cap(1).trimmed()] = r.cap(3).trimmed(); } pathsModel->setData(midx, QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole); } projectPathSelected(index); } void ProjectPathsWidget::setCurrentCompiler(const QString& name) { for (int i = 0 ; i < ui->compiler->count(); ++i) { if(ui->compiler->itemText(i) == name) { ui->compiler->setCurrentIndex(i); } } } CompilerPointer ProjectPathsWidget::currentCompiler() const { return ui->compiler->itemData(ui->compiler->currentIndex()).value(); } void ProjectPathsWidget::tabChanged(int idx) { if (idx == ParserArgumentsPage) { ui->batchEdit->setVisible(false); ui->compilerBox->setVisible(true); ui->configureLabel->setText(i18n("Configure C/C++ parser")); } else { ui->batchEdit->setVisible(true); ui->compilerBox->setVisible(false); ui->configureLabel->setText(i18n("Configure which macros and include directories/files will be added to the parser during project parsing:")); } } void ProjectPathsWidget::changeCompilerForPath() { for (int idx = 0; idx < pathsModel->rowCount(); idx++) { const QModelIndex midx = pathsModel->index(idx, 0); if (pathsModel->data(midx, Qt::DisplayRole) == ui->projectPaths->currentText()) { pathsModel->setData(midx, QVariant::fromValue(currentCompiler()), ProjectPathsModel::CompilerDataRole); break; } } } diff --git a/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp b/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp index 09c0aa5a73..1f7c133f94 100644 --- a/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp +++ b/languages/plugins/custom-definesandincludes/noprojectincludesanddefines/noprojectcustomincludepaths.cpp @@ -1,77 +1,77 @@ /* * Copyright 2014 Kevin Funk * * 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 "noprojectcustomincludepaths.h" #include "ui_noprojectcustomincludepaths.h" #include #include #include NoProjectCustomIncludePaths::NoProjectCustomIncludePaths(QWidget* parent) : QDialog(parent), m_ui(new Ui::CustomIncludePaths) { m_ui->setupUi(this); m_ui->storageDirectory->setMode(KFile::Directory); setWindowTitle(i18n("Setup Custom Include Paths")); connect(m_ui->directorySelector, &QPushButton::clicked, this, &NoProjectCustomIncludePaths::openAddIncludeDirectoryDialog); } void NoProjectCustomIncludePaths::setStorageDirectory(const QString& path) { m_ui->storageDirectory->setUrl(QUrl::fromLocalFile(path)); } QString NoProjectCustomIncludePaths::storageDirectory() const { return m_ui->storageDirectory->url().toLocalFile(); } void NoProjectCustomIncludePaths::appendCustomIncludePath(const QString& path) { m_ui->customIncludePaths->appendPlainText(path); } QStringList NoProjectCustomIncludePaths::customIncludePaths() const { const QString pathsText = m_ui->customIncludePaths->document()->toPlainText(); const QStringList paths = pathsText.split('\n', QString::SkipEmptyParts); return paths; } void NoProjectCustomIncludePaths::setCustomIncludePaths(const QStringList& paths) { m_ui->customIncludePaths->setPlainText(paths.join("\n")); } void NoProjectCustomIncludePaths::openAddIncludeDirectoryDialog() { - const QString dirName = QFileDialog::getExistingDirectory(this, tr("Select directory to include")); + const QString dirName = QFileDialog::getExistingDirectory(this, i18n("Select directory to include")); if (dirName.isEmpty()) return; appendCustomIncludePath(dirName); }