From 8dc4cb17c1708a39b4f513f0faa9f374ff9af067 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Mon, 1 Feb 2021 15:44:56 +0100 Subject: [PATCH] Designer: Re-use CppTools functionality ... for slot insertion logic. Fixes: QTCREATORBUG-8220 Change-Id: I3516a62d62174b64d557c82ce38a9cc334790efc Reviewed-by: Christian Stenger --- src/plugins/cppeditor/cppquickfixes.cpp | 356 ------------------ src/plugins/cpptools/cpptoolsreuse.cpp | 188 +++++++++ src/plugins/cpptools/cpptoolsreuse.h | 65 ++++ .../cpptools/insertionpointlocator.cpp | 136 ++++++- src/plugins/cpptools/insertionpointlocator.h | 16 + src/plugins/designer/qtcreatorintegration.cpp | 172 ++++----- 6 files changed, 468 insertions(+), 465 deletions(-) diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index f1a2d78b0ed..82698473bff 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -140,368 +140,12 @@ QString inlinePrefix(const QString &targetFile, const std::function &ext // different quick fixes. namespace { -class NSVisitor : public ASTVisitor -{ -public: - NSVisitor(const CppRefactoringFile *file, const QStringList &namespaces, int symbolPos) - : ASTVisitor(file->cppDocument()->translationUnit()), - m_file(file), - m_remainingNamespaces(namespaces), - m_symbolPos(symbolPos) - {} - - const QStringList remainingNamespaces() const { return m_remainingNamespaces; } - const NamespaceAST *firstNamespace() const { return m_firstNamespace; } - const AST *firstToken() const { return m_firstToken; } - const NamespaceAST *enclosingNamespace() const { return m_enclosingNamespace; } - -private: - bool preVisit(AST *ast) override - { - if (!m_firstToken) - m_firstToken = ast; - if (m_file->startOf(ast) >= m_symbolPos) - m_done = true; - return !m_done; - } - - bool visit(NamespaceAST *ns) override - { - if (!m_firstNamespace) - m_firstNamespace = ns; - if (m_remainingNamespaces.isEmpty()) { - m_done = true; - return false; - } - - QString name; - const Identifier * const id = translationUnit()->identifier(ns->identifier_token); - if (id) - name = QString::fromUtf8(id->chars(), id->size()); - if (name != m_remainingNamespaces.first()) - return false; - - if (!ns->linkage_body) { - m_done = true; - return false; - } - - m_enclosingNamespace = ns; - m_remainingNamespaces.removeFirst(); - return !m_remainingNamespaces.isEmpty(); - } - - void postVisit(AST *ast) override - { - if (ast == m_enclosingNamespace) - m_done = true; - } - - const CppRefactoringFile * const m_file; - const NamespaceAST *m_enclosingNamespace = nullptr; - const NamespaceAST *m_firstNamespace = nullptr; - const AST *m_firstToken = nullptr; - QStringList m_remainingNamespaces; - const int m_symbolPos; - bool m_done = false; -}; - -/** - * @brief The NSCheckerVisitor class checks which namespaces are missing for a given list - * of enclosing namespaces at a given position - */ -class NSCheckerVisitor : public ASTVisitor -{ -public: - NSCheckerVisitor(const CppRefactoringFile *file, const QStringList &namespaces, int symbolPos) - : ASTVisitor(file->cppDocument()->translationUnit()) - , m_file(file) - , m_remainingNamespaces(namespaces) - , m_symbolPos(symbolPos) - {} - /** - * @brief returns the names of the namespaces that are additionally needed at the symbolPos - * @return A list of namespace names, the outermost namespace at index 0 and the innermost - * at the last index - */ - const QStringList remainingNamespaces() const { return m_remainingNamespaces; } - -private: - bool preVisit(AST *ast) override - { - if (m_file->startOf(ast) >= m_symbolPos) - m_done = true; - return !m_done; - } - - void postVisit(AST *ast) override - { - if (!m_done && m_file->endOf(ast) > m_symbolPos) - m_done = true; - } - - bool visit(NamespaceAST *ns) override - { - if (m_remainingNamespaces.isEmpty()) - return false; - - QString name = getName(ns); - if (name != m_remainingNamespaces.first()) - return false; - - m_enteredNamespaces.push_back(ns); - m_remainingNamespaces.removeFirst(); - // if we reached the searched namespace we don't have to search deeper - return !m_remainingNamespaces.isEmpty(); - } - - bool visit(UsingDirectiveAST *usingNS) override - { - // example: we search foo::bar and get 'using namespace foo;using namespace foo::bar;' - const QString fullName = Overview{}.prettyName(usingNS->name->name); - const QStringList namespaces = fullName.split("::"); - if (namespaces.length() > m_remainingNamespaces.length()) - return false; - - // from other using namespace statements - const auto curList = m_usingsPerNamespace.find(currentNamespace()); - const bool isCurListValid = curList != m_usingsPerNamespace.end(); - - const bool startEqual = std::equal(namespaces.cbegin(), - namespaces.cend(), - m_remainingNamespaces.cbegin()); - if (startEqual) { - if (isCurListValid) { - if (namespaces.length() > curList->second.length()) { - // eg. we already have 'using namespace foo;' and - // now get 'using namespace foo::bar;' - curList->second = namespaces; - } - // the other case: first 'using namespace foo::bar;' and now 'using namespace foo;' - } else - m_usingsPerNamespace.emplace(currentNamespace(), namespaces); - } else if (isCurListValid) { - // ex: we have already 'using namespace foo;' and get 'using namespace bar;' now - QStringList newlist = curList->second; - newlist.append(namespaces); - if (newlist.length() <= m_remainingNamespaces.length()) { - const bool startEqual = std::equal(newlist.cbegin(), - newlist.cend(), - m_remainingNamespaces.cbegin()); - if (startEqual) - curList->second.append(namespaces); - } - } - return false; - } - - void endVisit(NamespaceAST *ns) override - { - // if the symbolPos was in the namespace and the - // namespace has no children, m_done should be true - postVisit(ns); - if (!m_done && currentNamespace() == ns) { - // we were not succesfull in this namespace, so undo all changes - m_remainingNamespaces.push_front(getName(currentNamespace())); - m_usingsPerNamespace.erase(currentNamespace()); - m_enteredNamespaces.pop_back(); - } - } - - void endVisit(TranslationUnitAST *) override - { - // the last node, create the final result - // we must handle like the following: We search for foo::bar and have: - // using namespace foo::bar; - // namespace foo { - // // cursor/symbolPos here - // } - if (m_remainingNamespaces.empty()) { - // we are already finished - return; - } - // find the longest combination of normal namespaces + using statements - int longestNamespaceList = 0; - int enteredNamespaceCount = 0; - // check 'using namespace ...;' statements in the global scope - const auto namespaces = m_usingsPerNamespace.find(nullptr); - if (namespaces != m_usingsPerNamespace.end()) - longestNamespaceList = namespaces->second.length(); - - for (auto ns : m_enteredNamespaces) { - ++enteredNamespaceCount; - const auto namespaces = m_usingsPerNamespace.find(ns); - int newListLength = enteredNamespaceCount; - if (namespaces != m_usingsPerNamespace.end()) - newListLength += namespaces->second.length(); - longestNamespaceList = std::max(newListLength, longestNamespaceList); - } - m_remainingNamespaces.erase(m_remainingNamespaces.begin(), - m_remainingNamespaces.begin() + longestNamespaceList - - m_enteredNamespaces.size()); - } - - QString getName(NamespaceAST *ns) - { - const Identifier *const id = translationUnit()->identifier(ns->identifier_token); - if (id) - return QString::fromUtf8(id->chars(), id->size()); - return {}; - } - - NamespaceAST *currentNamespace() - { - return m_enteredNamespaces.empty() ? nullptr : m_enteredNamespaces.back(); - } - - const CppRefactoringFile *const m_file; - QStringList m_remainingNamespaces; - const int m_symbolPos; - std::vector m_enteredNamespaces; - // track 'using namespace ...' statements - std::unordered_map m_usingsPerNamespace; - bool m_done = false; -}; - -/** - * @brief getListOfMissingNamespacesForLocation checks which namespaces are present at a given - * location and returns a list of namespace names that are needed to get the wanted namespace - * @param file The file of the location - * @param wantedNamespaces the namespace as list that should exists at the insert location - * @param loc The location that should be checked (the namespaces should be available there) - * @return A list of namespaces that are missing to reach the wanted namespaces. - */ -QStringList getListOfMissingNamespacesForLocation(const CppRefactoringFile *file, - const QStringList &wantedNamespaces, - InsertionLocation loc) -{ - NSCheckerVisitor visitor(file, wantedNamespaces, file->position(loc.line(), loc.column())); - visitor.accept(file->cppDocument()->translationUnit()->ast()); - return visitor.remainingNamespaces(); -} - enum DefPos { DefPosInsideClass, DefPosOutsideClass, DefPosImplementationFile }; -/** - * @brief getNamespaceNames Returns a list of namespaces for an enclosing namespaces of a - * namespace (contains the namespace itself) - * @param firstNamespace the starting namespace (included in the list) - * @return the enclosing namespaces, the outermost namespace is at the first index, the innermost - * at the last index - */ -QStringList getNamespaceNames(const Namespace *firstNamespace) -{ - QStringList namespaces; - for (const Namespace *scope = firstNamespace; scope; scope = scope->enclosingNamespace()) { - if (scope->name() && scope->name()->identifier()) { - namespaces.prepend(QString::fromUtf8(scope->name()->identifier()->chars(), - scope->name()->identifier()->size())); - } else { - namespaces.prepend(""); // an unnamed namespace - } - } - namespaces.pop_front(); // the "global namespace" is one namespace, but not an unnamed - return namespaces; -} - -/** - * @brief getNamespaceNames Returns a list of enclosing namespaces for a symbol - * @param symbol a symbol from which we want the enclosing namespaces - * @return the enclosing namespaces, the outermost namespace is at the first index, the innermost - * at the last index - */ -QStringList getNamespaceNames(const Symbol *symbol) -{ - return getNamespaceNames(symbol->enclosingNamespace()); -} - -// TODO: We should use the "CreateMissing" approach everywhere. -enum class NamespaceHandling { CreateMissing, Ignore }; -InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, - const bool useSymbolFinder, - NamespaceHandling namespaceHandling, - const CppRefactoringChanges &refactoring, - const QString &fileName, - QStringList *insertedNamespaces = nullptr) -{ - QTC_ASSERT(symbol, return InsertionLocation()); - - CppRefactoringFilePtr file = refactoring.file(fileName); - QStringList requiredNamespaces; - if (namespaceHandling == NamespaceHandling::CreateMissing) { - requiredNamespaces = getNamespaceNames(symbol); - } - - // Try to find optimal location - // FIXME: The locator should not return a valid location if the namespaces don't match - // (or provide enough context). - const InsertionPointLocator locator(refactoring); - const QList list - = locator.methodDefinition(symbol, useSymbolFinder, fileName); - const bool isHeader = ProjectFile::isHeader(ProjectFile::classify(fileName)); - const bool hasIncludeGuard = isHeader - && !file->cppDocument()->includeGuardMacroName().isEmpty(); - int lastLine; - if (hasIncludeGuard) { - const TranslationUnit * const tu = file->cppDocument()->translationUnit(); - tu->getTokenStartPosition(tu->ast()->lastToken(), &lastLine); - } - int i = 0; - for ( ; i < list.count(); ++i) { - InsertionLocation location = list.at(i); - if (!location.isValid() || location.fileName() != fileName) - continue; - if (hasIncludeGuard && location.line() == lastLine) - continue; - if (!requiredNamespaces.isEmpty()) { - QStringList missing = getListOfMissingNamespacesForLocation(file.get(), - requiredNamespaces, - location); - if (!missing.isEmpty()) - continue; - } - return location; - } - - // ...failed, - // if class member try to get position right after class - int line = 0, column = 0; - if (Class *clazz = symbol->enclosingClass()) { - if (symbol->fileName() == fileName.toUtf8()) { - file->cppDocument()->translationUnit()->getPosition(clazz->endOffset(), &line, &column); - if (line != 0) { - ++column; // Skipping the ";" - return InsertionLocation(fileName, QLatin1String("\n\n"), QLatin1String(""), - line, column); - } - } - } - - // fall through: position at end of file, unless we find a matching namespace - const QTextDocument *doc = file->document(); - int pos = qMax(0, doc->characterCount() - 1); - QString prefix = "\n\n"; - QString suffix = "\n\n"; - NSVisitor visitor(file.data(), requiredNamespaces, pos); - visitor.accept(file->cppDocument()->translationUnit()->ast()); - if (visitor.enclosingNamespace()) - pos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1; - for (const QString &ns : visitor.remainingNamespaces()) { - prefix += "namespace " + ns + " {\n"; - suffix += "}\n"; - } - if (insertedNamespaces) - *insertedNamespaces = visitor.remainingNamespaces(); - - //TODO watch for moc-includes - - file->lineAndColumn(pos, &line, &column); - return InsertionLocation(fileName, prefix, suffix, line, column); -} inline bool isQtStringLiteral(const QByteArray &id) { diff --git a/src/plugins/cpptools/cpptoolsreuse.cpp b/src/plugins/cpptools/cpptoolsreuse.cpp index 9267a8171cb..b618fea8a7d 100644 --- a/src/plugins/cpptools/cpptoolsreuse.cpp +++ b/src/plugins/cpptools/cpptoolsreuse.cpp @@ -26,6 +26,7 @@ #include "cpptoolsreuse.h" #include "cppcodemodelsettings.h" +#include "cpprefactoringchanges.h" #include "cpptoolsconstants.h" #include "cpptoolsplugin.h" @@ -384,4 +385,191 @@ ClangDiagnosticConfigsModel diagnosticConfigsModel() return diagnosticConfigsModel(CppTools::codeModelSettings()->clangCustomDiagnosticConfigs()); } +NSVisitor::NSVisitor(const CppRefactoringFile *file, const QStringList &namespaces, int symbolPos) + : ASTVisitor(file->cppDocument()->translationUnit()), + m_file(file), + m_remainingNamespaces(namespaces), + m_symbolPos(symbolPos) +{} + +bool CppTools::NSVisitor::preVisit(AST *ast) +{ + if (!m_firstToken) + m_firstToken = ast; + if (m_file->startOf(ast) >= m_symbolPos) + m_done = true; + return !m_done; +} + +bool NSVisitor::visit(NamespaceAST *ns) +{ + if (!m_firstNamespace) + m_firstNamespace = ns; + if (m_remainingNamespaces.isEmpty()) { + m_done = true; + return false; + } + + QString name; + const Identifier * const id = translationUnit()->identifier(ns->identifier_token); + if (id) + name = QString::fromUtf8(id->chars(), id->size()); + if (name != m_remainingNamespaces.first()) + return false; + + if (!ns->linkage_body) { + m_done = true; + return false; + } + + m_enclosingNamespace = ns; + m_remainingNamespaces.removeFirst(); + return !m_remainingNamespaces.isEmpty(); +} + +void NSVisitor::postVisit(AST *ast) +{ + if (ast == m_enclosingNamespace) + m_done = true; +} + +/** + * @brief The NSCheckerVisitor class checks which namespaces are missing for a given list + * of enclosing namespaces at a given position + */ +NSCheckerVisitor::NSCheckerVisitor(const CppRefactoringFile *file, const QStringList &namespaces, + int symbolPos) + : ASTVisitor(file->cppDocument()->translationUnit()) + , m_file(file) + , m_remainingNamespaces(namespaces) + , m_symbolPos(symbolPos) +{} + +bool NSCheckerVisitor::preVisit(AST *ast) +{ + if (m_file->startOf(ast) >= m_symbolPos) + m_done = true; + return !m_done; +} + +void NSCheckerVisitor::postVisit(AST *ast) +{ + if (!m_done && m_file->endOf(ast) > m_symbolPos) + m_done = true; +} + +bool NSCheckerVisitor::visit(NamespaceAST *ns) +{ + if (m_remainingNamespaces.isEmpty()) + return false; + + QString name = getName(ns); + if (name != m_remainingNamespaces.first()) + return false; + + m_enteredNamespaces.push_back(ns); + m_remainingNamespaces.removeFirst(); + // if we reached the searched namespace we don't have to search deeper + return !m_remainingNamespaces.isEmpty(); +} + +bool NSCheckerVisitor::visit(UsingDirectiveAST *usingNS) +{ + // example: we search foo::bar and get 'using namespace foo;using namespace foo::bar;' + const QString fullName = Overview{}.prettyName(usingNS->name->name); + const QStringList namespaces = fullName.split("::"); + if (namespaces.length() > m_remainingNamespaces.length()) + return false; + + // from other using namespace statements + const auto curList = m_usingsPerNamespace.find(currentNamespace()); + const bool isCurListValid = curList != m_usingsPerNamespace.end(); + + const bool startEqual = std::equal(namespaces.cbegin(), + namespaces.cend(), + m_remainingNamespaces.cbegin()); + if (startEqual) { + if (isCurListValid) { + if (namespaces.length() > curList->second.length()) { + // eg. we already have 'using namespace foo;' and + // now get 'using namespace foo::bar;' + curList->second = namespaces; + } + // the other case: first 'using namespace foo::bar;' and now 'using namespace foo;' + } else + m_usingsPerNamespace.emplace(currentNamespace(), namespaces); + } else if (isCurListValid) { + // ex: we have already 'using namespace foo;' and get 'using namespace bar;' now + QStringList newlist = curList->second; + newlist.append(namespaces); + if (newlist.length() <= m_remainingNamespaces.length()) { + const bool startEqual = std::equal(newlist.cbegin(), + newlist.cend(), + m_remainingNamespaces.cbegin()); + if (startEqual) + curList->second.append(namespaces); + } + } + return false; +} + +void NSCheckerVisitor::endVisit(NamespaceAST *ns) +{ + // if the symbolPos was in the namespace and the + // namespace has no children, m_done should be true + postVisit(ns); + if (!m_done && currentNamespace() == ns) { + // we were not succesfull in this namespace, so undo all changes + m_remainingNamespaces.push_front(getName(currentNamespace())); + m_usingsPerNamespace.erase(currentNamespace()); + m_enteredNamespaces.pop_back(); + } +} + +void NSCheckerVisitor::endVisit(TranslationUnitAST *) +{ + // the last node, create the final result + // we must handle like the following: We search for foo::bar and have: + // using namespace foo::bar; + // namespace foo { + // // cursor/symbolPos here + // } + if (m_remainingNamespaces.empty()) { + // we are already finished + return; + } + // find the longest combination of normal namespaces + using statements + int longestNamespaceList = 0; + int enteredNamespaceCount = 0; + // check 'using namespace ...;' statements in the global scope + const auto namespaces = m_usingsPerNamespace.find(nullptr); + if (namespaces != m_usingsPerNamespace.end()) + longestNamespaceList = namespaces->second.length(); + + for (auto ns : m_enteredNamespaces) { + ++enteredNamespaceCount; + const auto namespaces = m_usingsPerNamespace.find(ns); + int newListLength = enteredNamespaceCount; + if (namespaces != m_usingsPerNamespace.end()) + newListLength += namespaces->second.length(); + longestNamespaceList = std::max(newListLength, longestNamespaceList); + } + m_remainingNamespaces.erase(m_remainingNamespaces.begin(), + m_remainingNamespaces.begin() + longestNamespaceList + - m_enteredNamespaces.size()); +} + +QString NSCheckerVisitor::getName(NamespaceAST *ns) +{ + const Identifier *const id = translationUnit()->identifier(ns->identifier_token); + if (id) + return QString::fromUtf8(id->chars(), id->size()); + return {}; +} + +NamespaceAST *NSCheckerVisitor::currentNamespace() +{ + return m_enteredNamespaces.empty() ? nullptr : m_enteredNamespaces.back(); +} + } // CppTools diff --git a/src/plugins/cpptools/cpptoolsreuse.h b/src/plugins/cpptools/cpptoolsreuse.h index 615095e14ea..02633f91eaf 100644 --- a/src/plugins/cpptools/cpptoolsreuse.h +++ b/src/plugins/cpptools/cpptoolsreuse.h @@ -32,6 +32,7 @@ #include #include +#include #include QT_BEGIN_NAMESPACE @@ -47,6 +48,7 @@ class LookupContext; } // namespace CPlusPlus namespace CppTools { +class CppRefactoringFile; void CPPTOOLS_EXPORT moveCursorToEndOfIdentifier(QTextCursor *tc); void CPPTOOLS_EXPORT moveCursorToStartOfIdentifier(QTextCursor *tc); @@ -86,4 +88,67 @@ ClangDiagnosticConfigsModel CPPTOOLS_EXPORT diagnosticConfigsModel(); ClangDiagnosticConfigsModel CPPTOOLS_EXPORT diagnosticConfigsModel(const CppTools::ClangDiagnosticConfigs &customConfigs); + +QStringList CPPTOOLS_EXPORT getNamespaceNames(const CPlusPlus::Namespace *firstNamespace); +QStringList CPPTOOLS_EXPORT getNamespaceNames(const CPlusPlus::Symbol *symbol); + +class CPPTOOLS_EXPORT NSVisitor : public CPlusPlus::ASTVisitor +{ +public: + NSVisitor(const CppRefactoringFile *file, const QStringList &namespaces, int symbolPos); + + const QStringList remainingNamespaces() const { return m_remainingNamespaces; } + const CPlusPlus::NamespaceAST *firstNamespace() const { return m_firstNamespace; } + const CPlusPlus::AST *firstToken() const { return m_firstToken; } + const CPlusPlus::NamespaceAST *enclosingNamespace() const { return m_enclosingNamespace; } + +private: + bool preVisit(CPlusPlus::AST *ast) override; + bool visit(CPlusPlus::NamespaceAST *ns) override; + void postVisit(CPlusPlus::AST *ast) override; + + const CppRefactoringFile * const m_file; + const CPlusPlus::NamespaceAST *m_enclosingNamespace = nullptr; + const CPlusPlus::NamespaceAST *m_firstNamespace = nullptr; + const CPlusPlus::AST *m_firstToken = nullptr; + QStringList m_remainingNamespaces; + const int m_symbolPos; + bool m_done = false; +}; + +class CPPTOOLS_EXPORT NSCheckerVisitor : public CPlusPlus::ASTVisitor +{ +public: + NSCheckerVisitor(const CppRefactoringFile *file, const QStringList &namespaces, int symbolPos); + + /** + * @brief returns the names of the namespaces that are additionally needed at the symbolPos + * @return A list of namespace names, the outermost namespace at index 0 and the innermost + * at the last index + */ + const QStringList remainingNamespaces() const { return m_remainingNamespaces; } + +private: + bool preVisit(CPlusPlus::AST *ast) override; + void postVisit(CPlusPlus::AST *ast) override; + bool visit(CPlusPlus::NamespaceAST *ns) override; + bool visit(CPlusPlus::UsingDirectiveAST *usingNS) override; + void endVisit(CPlusPlus::NamespaceAST *ns) override; + void endVisit(CPlusPlus::TranslationUnitAST *) override; + + QString getName(CPlusPlus::NamespaceAST *ns); + CPlusPlus::NamespaceAST *currentNamespace(); + + const CppRefactoringFile *const m_file; + QStringList m_remainingNamespaces; + const int m_symbolPos; + std::vector m_enteredNamespaces; + + // track 'using namespace ...' statements + std::unordered_map m_usingsPerNamespace; + + bool m_done = false; +}; + + } // CppTools diff --git a/src/plugins/cpptools/insertionpointlocator.cpp b/src/plugins/cpptools/insertionpointlocator.cpp index d258937cb7d..c6f095f6108 100644 --- a/src/plugins/cpptools/insertionpointlocator.cpp +++ b/src/plugins/cpptools/insertionpointlocator.cpp @@ -38,8 +38,8 @@ #include using namespace CPlusPlus; -using namespace CppTools; +namespace CppTools { namespace { static int ordering(InsertionPointLocator::AccessSpec xsSpec) @@ -723,3 +723,137 @@ const QList InsertionPointLocator::methodDefinition( return result; } + +/** + * @brief getListOfMissingNamespacesForLocation checks which namespaces are present at a given + * location and returns a list of namespace names that are needed to get the wanted namespace + * @param file The file of the location + * @param wantedNamespaces the namespace as list that should exists at the insert location + * @param loc The location that should be checked (the namespaces should be available there) + * @return A list of namespaces that are missing to reach the wanted namespaces. + */ +static QStringList getListOfMissingNamespacesForLocation(const CppRefactoringFile *file, + const QStringList &wantedNamespaces, + InsertionLocation loc) +{ + NSCheckerVisitor visitor(file, wantedNamespaces, file->position(loc.line(), loc.column())); + visitor.accept(file->cppDocument()->translationUnit()->ast()); + return visitor.remainingNamespaces(); +} + +/** + * @brief getNamespaceNames Returns a list of namespaces for an enclosing namespaces of a + * namespace (contains the namespace itself) + * @param firstNamespace the starting namespace (included in the list) + * @return the enclosing namespaces, the outermost namespace is at the first index, the innermost + * at the last index + */ +QStringList getNamespaceNames(const Namespace *firstNamespace) +{ + QStringList namespaces; + for (const Namespace *scope = firstNamespace; scope; scope = scope->enclosingNamespace()) { + if (scope->name() && scope->name()->identifier()) { + namespaces.prepend(QString::fromUtf8(scope->name()->identifier()->chars(), + scope->name()->identifier()->size())); + } else { + namespaces.prepend(""); // an unnamed namespace + } + } + namespaces.pop_front(); // the "global namespace" is one namespace, but not an unnamed + return namespaces; +} + +/** + * @brief getNamespaceNames Returns a list of enclosing namespaces for a symbol + * @param symbol a symbol from which we want the enclosing namespaces + * @return the enclosing namespaces, the outermost namespace is at the first index, the innermost + * at the last index + */ +QStringList getNamespaceNames(const Symbol *symbol) +{ + return getNamespaceNames(symbol->enclosingNamespace()); +} + +InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, + const bool useSymbolFinder, + NamespaceHandling namespaceHandling, + const CppRefactoringChanges &refactoring, + const QString &fileName, + QStringList *insertedNamespaces) +{ + QTC_ASSERT(symbol, return InsertionLocation()); + + CppRefactoringFilePtr file = refactoring.file(fileName); + QStringList requiredNamespaces; + if (namespaceHandling == NamespaceHandling::CreateMissing) { + requiredNamespaces = getNamespaceNames(symbol); + } + + // Try to find optimal location + // FIXME: The locator should not return a valid location if the namespaces don't match + // (or provide enough context). + const InsertionPointLocator locator(refactoring); + const QList list + = locator.methodDefinition(symbol, useSymbolFinder, fileName); + const bool isHeader = ProjectFile::isHeader(ProjectFile::classify(fileName)); + const bool hasIncludeGuard = isHeader + && !file->cppDocument()->includeGuardMacroName().isEmpty(); + int lastLine; + if (hasIncludeGuard) { + const TranslationUnit * const tu = file->cppDocument()->translationUnit(); + tu->getTokenStartPosition(tu->ast()->lastToken(), &lastLine); + } + int i = 0; + for ( ; i < list.count(); ++i) { + InsertionLocation location = list.at(i); + if (!location.isValid() || location.fileName() != fileName) + continue; + if (hasIncludeGuard && location.line() == lastLine) + continue; + if (!requiredNamespaces.isEmpty()) { + QStringList missing = getListOfMissingNamespacesForLocation(file.get(), + requiredNamespaces, + location); + if (!missing.isEmpty()) + continue; + } + return location; + } + + // ...failed, + // if class member try to get position right after class + int line = 0, column = 0; + if (Class *clazz = symbol->enclosingClass()) { + if (symbol->fileName() == fileName.toUtf8()) { + file->cppDocument()->translationUnit()->getPosition(clazz->endOffset(), &line, &column); + if (line != 0) { + ++column; // Skipping the ";" + return InsertionLocation(fileName, QLatin1String("\n\n"), QLatin1String(""), + line, column); + } + } + } + + // fall through: position at end of file, unless we find a matching namespace + const QTextDocument *doc = file->document(); + int pos = qMax(0, doc->characterCount() - 1); + QString prefix = "\n\n"; + QString suffix = "\n\n"; + NSVisitor visitor(file.data(), requiredNamespaces, pos); + visitor.accept(file->cppDocument()->translationUnit()->ast()); + if (visitor.enclosingNamespace()) + pos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1; + for (const QString &ns : visitor.remainingNamespaces()) { + prefix += "namespace " + ns + " {\n"; + suffix += "}\n"; + } + if (insertedNamespaces) + *insertedNamespaces = visitor.remainingNamespaces(); + + //TODO watch for moc-includes + + file->lineAndColumn(pos, &line, &column); + return InsertionLocation(fileName, prefix, suffix, line, column); +} + +} // namespace CppTools; diff --git a/src/plugins/cpptools/insertionpointlocator.h b/src/plugins/cpptools/insertionpointlocator.h index c20c45151bc..f7a647ba143 100644 --- a/src/plugins/cpptools/insertionpointlocator.h +++ b/src/plugins/cpptools/insertionpointlocator.h @@ -28,6 +28,12 @@ #include "cpptools_global.h" #include "cpprefactoringchanges.h" +namespace CPlusPlus { +class Namespace; +class NamespaceAST; +class Symbol; +} // namespace CPlusPlus + namespace CppTools { class CPPTOOLS_EXPORT InsertionLocation @@ -125,4 +131,14 @@ private: CppRefactoringChanges m_refactoringChanges; }; +// TODO: We should use the "CreateMissing" approach everywhere. +enum class NamespaceHandling { CreateMissing, Ignore }; +InsertionLocation CPPTOOLS_EXPORT +insertLocationForMethodDefinition(CPlusPlus::Symbol *symbol, + const bool useSymbolFinder, + NamespaceHandling namespaceHandling, + const CppRefactoringChanges &refactoring, + const QString &fileName, + QStringList *insertedNamespaces = nullptr); + } // namespace CppTools diff --git a/src/plugins/designer/qtcreatorintegration.cpp b/src/plugins/designer/qtcreatorintegration.cpp index ba4dcc9c5f2..9ab38d9b088 100644 --- a/src/plugins/designer/qtcreatorintegration.cpp +++ b/src/plugins/designer/qtcreatorintegration.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -167,7 +168,7 @@ QString fullyQualifiedName(const LookupContext &context, const Name *name, Scope // containing a member of the desired class type) or inheriting the desired class // in case of forms using the Multiple Inheritance approach static const Class *findClass(const Namespace *parentNameSpace, const LookupContext &context, - const QString &className, QString *namespaceName) + const QString &className) { if (Designer::Constants::Internal::debug) qDebug() << Q_FUNC_INFO << className; @@ -204,14 +205,9 @@ static const Class *findClass(const Namespace *parentNameSpace, const LookupCont } else { // Check namespaces if (const Namespace *ns = sym->asNamespace()) { - QString tempNS = *namespaceName; - tempNS += o.prettyName(ns->name()); - tempNS += "::"; - if (const Class *cl = findClass(ns, context, className, &tempNS)) { - *namespaceName = tempNS; + if (const Class *cl = findClass(ns, context, className)) return cl; - } - } // member is namespave + } // member is namespace } // member is no class } // for members return nullptr; @@ -247,22 +243,6 @@ static Function *findDeclaration(const Class *cl, const QString &functionName) return nullptr; } -// TODO: remove me, this is taken from cppeditor.cpp. Find some common place for this function -static Document::Ptr findDefinition(Function *functionDeclaration, int *line) -{ - CppTools::CppModelManager *cppModelManager = CppTools::CppModelManager::instance(); - const Snapshot snapshot = cppModelManager->snapshot(); - CppTools::SymbolFinder symbolFinder; - if (Function *fun = symbolFinder.findMatchingDefinition(functionDeclaration, snapshot)) { - if (line) - *line = fun->line(); - - return snapshot.document(QString::fromUtf8(fun->fileName(), fun->fileNameLength())); - } - - return Document::Ptr(); -} - static inline BaseTextEditor *editorAt(const QString &fileName, int line, int column) { return qobject_cast(Core::EditorManager::openEditorAt(fileName, line, column, @@ -297,50 +277,6 @@ static void addDeclaration(const Snapshot &snapshot, } } -static Document::Ptr addDefinition(const Snapshot &docTable, - const QString &headerFileName, - const QString &className, - const QString &functionName, - int *line) -{ - const QString definition = "\nvoid " + className + "::" + functionName - + "\n{\n" + QString(indentation, ' ') + "\n}\n"; - - // we find all documents which include headerFileName - const QList docList = findDocumentsIncluding(docTable, headerFileName, false); - if (docList.isEmpty()) - return Document::Ptr(); - - QFileInfo headerFI(headerFileName); - const QString headerBaseName = headerFI.completeBaseName(); - for (const Document::Ptr &doc : docList) { - const QFileInfo sourceFI(doc->fileName()); - // we take only those documents which have the same filename - if (headerBaseName == sourceFI.baseName()) { - // - //! \todo change this to use the Refactoring changes. - // - - if (BaseTextEditor *editor = editorAt(doc->fileName(), 0, 0)) { - - // - //! \todo use the InsertionPointLocator to insert at the correct place. - // (we'll have to extend that class first to do definition insertions) - - const QString contents = editor->textDocument()->plainText(); - int column; - editor->convertPosition(contents.length(), line, &column); - // gotoLine accepts 0-based column. - editor->gotoLine(*line, column - 1); - editor->insert(definition); - *line += 1; - } - return doc; - } - } - return Document::Ptr(); -} - static QString addConstRefIfNeeded(const QString &argument) { if (argument.startsWith("const ") || argument.endsWith('&') || argument.endsWith('*')) @@ -417,14 +353,14 @@ using ClassDocumentPtrPair = QPair; static ClassDocumentPtrPair findClassRecursively(const LookupContext &context, const QString &className, - unsigned maxIncludeDepth, QString *namespaceName) + unsigned maxIncludeDepth) { const Document::Ptr doc = context.thisDocument(); const Snapshot docTable = context.snapshot(); if (Designer::Constants::Internal::debug) qDebug() << Q_FUNC_INFO << doc->fileName() << className << maxIncludeDepth; // Check document - if (const Class *cl = findClass(doc->globalNamespace(), context, className, namespaceName)) + if (const Class *cl = findClass(doc->globalNamespace(), context, className)) return ClassDocumentPtrPair(cl, doc); if (maxIncludeDepth) { // Check the includes @@ -436,7 +372,7 @@ static ClassDocumentPtrPair const Document::Ptr &includeDoc = it.value(); LookupContext context(includeDoc, docTable); const ClassDocumentPtrPair irc = findClassRecursively(context, className, - recursionMaxIncludeDepth, namespaceName); + recursionMaxIncludeDepth); if (irc.first) return irc; } @@ -556,16 +492,14 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName, // Find the class definition (ui class defined as member or base class) // in the file itself or in the directly included files (order 1). - QString namespaceName; const Class *cl = nullptr; - Document::Ptr doc; - + Document::Ptr declDoc; for (const Document::Ptr &d : qAsConst(docMap)) { LookupContext context(d, docTable); - const ClassDocumentPtrPair cd = findClassRecursively(context, uiClass, 1u , &namespaceName); + const ClassDocumentPtrPair cd = findClassRecursively(context, uiClass, 1u); if (cd.first) { cl = cd.first; - doc = cd.second; + declDoc = cd.second; break; } } @@ -574,47 +508,69 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName, return false; } - Overview o; - const QString className = namespaceName + o.prettyName(cl->name()); - if (Designer::Constants::Internal::debug) - qDebug() << "Found class " << className << doc->fileName(); - const QString functionName = "on_" + objectName + '_' + signalSignature; const QString functionNameWithParameterNames = addParameterNames(functionName, parameterNames); if (Designer::Constants::Internal::debug) - qDebug() << Q_FUNC_INFO << "Found " << uiClass << doc->fileName() << " checking " << functionName << functionNameWithParameterNames; + qDebug() << Q_FUNC_INFO << "Found " << uiClass << declDoc->fileName() << " checking " << functionName << functionNameWithParameterNames; - int line = 0; - Document::Ptr sourceDoc; - - if (Function *fun = findDeclaration(cl, functionName)) { - sourceDoc = findDefinition(fun, &line); - if (!sourceDoc) { - // add function definition to cpp file - sourceDoc = addDefinition(docTable, doc->fileName(), className, functionNameWithParameterNames, &line); - } - } else { + Function *fun = findDeclaration(cl, functionName); + QString declFilePath; + if (!fun) { // add function declaration to cl - CppTools::WorkingCopy workingCopy = - CppTools::CppModelManager::instance()->workingCopy(); - const QString fileName = doc->fileName(); - getParsedDocument(fileName, workingCopy, docTable); - addDeclaration(docTable, fileName, cl, functionNameWithParameterNames); + CppTools::WorkingCopy workingCopy = CppTools::CppModelManager::instance()->workingCopy(); + declFilePath = declDoc->fileName(); + getParsedDocument(declFilePath, workingCopy, docTable); + addDeclaration(docTable, declFilePath, cl, functionNameWithParameterNames); - // add function definition to cpp file - sourceDoc = addDefinition(docTable, fileName, className, functionNameWithParameterNames, &line); + // Re-load C++ documents. + QList filePaths; + for (auto it = docTable.begin(); it != docTable.end(); ++it) + filePaths << it.key(); + workingCopy = CppTools::CppModelManager::instance()->workingCopy(); + docTable = CppTools::CppModelManager::instance()->snapshot(); + newDocTable = {}; + for (const auto &file : qAsConst(filePaths)) { + const Document::Ptr doc = docTable.document(file); + if (doc) + newDocTable.insert(doc); + } + docTable = newDocTable; + getParsedDocument(declFilePath, workingCopy, docTable); + const Document::Ptr headerDoc = docTable.document(declFilePath); + QTC_ASSERT(headerDoc, return false); + LookupContext context(headerDoc, docTable); + cl = findClass(headerDoc->globalNamespace(), context, uiClass); + QTC_ASSERT(cl, return false); + fun = findDeclaration(cl, functionName); + } else { + declFilePath = QLatin1String(fun->fileName()); + } + QTC_ASSERT(fun, return false); + + CppTools::CppRefactoringChanges refactoring(docTable); + CppTools::SymbolFinder symbolFinder; + if (symbolFinder.findMatchingDefinition(fun, docTable, true)) + return true; + const QString implFilePath = CppTools::correspondingHeaderOrSource(declFilePath); + const CppTools::InsertionLocation location = CppTools::insertLocationForMethodDefinition + (fun, false, CppTools::NamespaceHandling::CreateMissing, refactoring, implFilePath); + + if (BaseTextEditor *editor = editorAt(location.fileName(), location.line(), location.column())) { + Overview o; + const QString className = o.prettyName(cl->name()); + const QString definition = location.prefix() + "void " + className + "::" + + functionNameWithParameterNames + "\n{\n" + QString(indentation, ' ') + "\n}\n" + + location.suffix(); + editor->insert(definition); + Core::EditorManager::openEditorAt(location.fileName(), + location.line() + location.prefix().count('\n') + 2, + indentation); + return true; } - if (!sourceDoc) { - *errorMessage = tr("Unable to add the method definition."); - return false; - } - - // jump to function definition, position within code - Core::EditorManager::openEditorAt(sourceDoc->fileName(), line + 2, indentation); - - return true; + *errorMessage = tr("Unable to add the method definition."); + return false; } void QtCreatorIntegration::slotSyncSettingsToDesigner()