diff --git a/languages/clang/CMakeLists.txt b/languages/clang/CMakeLists.txt --- a/languages/clang/CMakeLists.txt +++ b/languages/clang/CMakeLists.txt @@ -56,6 +56,8 @@ codegen/adaptsignatureaction.cpp codegen/adaptsignatureassistant.cpp codegen/codegenhelper.cpp + codegen/clangrefactoring.cpp + codegen/sourcemanipulation.cpp duchain/builder.cpp duchain/clangdiagnosticevaluator.cpp diff --git a/languages/clang/clangsupport.h b/languages/clang/clangsupport.h --- a/languages/clang/clangsupport.h +++ b/languages/clang/clangsupport.h @@ -32,9 +32,9 @@ #include class ClangIndex; +class ClangRefactoring; namespace KDevelop { -class BasicRefactoring; class IDocument; } @@ -92,7 +92,7 @@ private: KDevelop::ICodeHighlighting *m_highlighting; - KDevelop::BasicRefactoring *m_refactoring; + ClangRefactoring *m_refactoring; QScopedPointer m_index; }; diff --git a/languages/clang/clangsupport.cpp b/languages/clang/clangsupport.cpp --- a/languages/clang/clangsupport.cpp +++ b/languages/clang/clangsupport.cpp @@ -41,6 +41,7 @@ #include #include +#include "codegen/clangrefactoring.h" #include "codegen/adaptsignatureassistant.h" #include "duchain/documentfinderhelpers.h" #include "duchain/clangindex.h" @@ -53,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -184,7 +184,7 @@ ClangIntegration::DUChainUtils::registerDUChainItems(); m_highlighting = new ClangHighlighting(this); - m_refactoring = new BasicRefactoring(this); + m_refactoring = new ClangRefactoring(this); m_index.reset(new ClangIndex); auto model = new KDevelop::CodeCompletion( this, new ClangCodeCompletionModel(m_index.data(), this), name() ); @@ -277,7 +277,13 @@ renameDeclarationAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); actions.setDefaultShortcut(renameDeclarationAction, Qt::CTRL | Qt::SHIFT | Qt::Key_R); connect(renameDeclarationAction, &QAction::triggered, - m_refactoring, &BasicRefactoring::executeRenameAction); + m_refactoring, &ClangRefactoring::executeRenameAction); + + QAction* moveIntoSourceAction = actions.addAction(QStringLiteral("code_move_definition")); + moveIntoSourceAction->setText(i18n("Move into Source")); + actions.setDefaultShortcut(moveIntoSourceAction, Qt::CTRL | Qt::ALT | Qt::Key_S); + connect(moveIntoSourceAction, &QAction::triggered, + m_refactoring, &ClangRefactoring::executeMoveIntoSourceAction); } KDevelop::ContextMenuExtension ClangSupport::contextMenuExtension(KDevelop::Context* context) diff --git a/languages/clang/codegen/adaptsignatureaction.cpp b/languages/clang/codegen/adaptsignatureaction.cpp --- a/languages/clang/codegen/adaptsignatureaction.cpp +++ b/languages/clang/codegen/adaptsignatureaction.cpp @@ -25,89 +25,16 @@ #include #include -#include #include #include #include #include -#include #include #include using namespace KDevelop; -QString makeSignatureString(const Declaration* functionDecl, const Signature& signature, const bool editingDefinition) -{ - if (!functionDecl || !functionDecl->internalContext()) { - return {}; - } - const auto visibilityFrom = functionDecl->internalContext()->parentContext(); - if (!visibilityFrom) { - return {}; - } - - QString ret; - - if (!editingDefinition) { - auto classMember = dynamic_cast(functionDecl); - if (classMember && classMember->isStatic()) { - ret += QLatin1String("static "); - } - } - - // constructors don't have a return type - if (signature.returnType.isValid()) { - ret += CodegenHelper::simplifiedTypeString(signature.returnType.abstractType(), - visibilityFrom); - ret += QLatin1Char(' '); - } - - ret += editingDefinition ? functionDecl->qualifiedIdentifier().toString() : functionDecl->identifier().toString(); - - ret += QLatin1Char('('); - int pos = 0; - - foreach(const ParameterItem &item, signature.parameters) - { - if (pos != 0) { - ret += QLatin1String(", "); - } - - ///TODO: merge common code with helpers.cpp::createArgumentList - AbstractType::Ptr type = item.first.abstractType(); - - QString arrayAppendix; - ArrayType::Ptr arrayType; - while ((arrayType = type.cast())) { - type = arrayType->elementType(); - //note: we have to prepend since we iterate from outside, i.e. from right to left. - if (arrayType->dimension()) { - arrayAppendix.prepend(QStringLiteral("[%1]").arg(arrayType->dimension())); - } else { - // dimensionless - arrayAppendix.prepend(QLatin1String("[]")); - } - } - ret += CodegenHelper::simplifiedTypeString(type, - visibilityFrom); - - if (!item.second.isEmpty()) { - ret += QLatin1Char(' ') + item.second; - } - ret += arrayAppendix; - if (signature.defaultParams.size() > pos && !signature.defaultParams[pos].isEmpty()) { - ret += QLatin1String(" = ") + signature.defaultParams[pos]; - } - ++pos; - } - ret += QLatin1Char(')'); - if (signature.isConst) { - ret += QLatin1String(" const"); - } - return ret; -} - AdaptSignatureAction::AdaptSignatureAction(const DeclarationId& definitionId, ReferencedTopDUContext definitionContext, const Signature& oldSignature, @@ -141,8 +68,8 @@ } return i18n("Update %1 signature\nfrom: %2\nto: %3", m_editingDefinition ? i18n("declaration") : i18n("definition"), - makeSignatureString(declaration, m_oldSignature, m_editingDefinition), - makeSignatureString(declaration, m_newSignature, !m_editingDefinition)); + CodegenHelper::makeSignatureString(declaration, m_oldSignature, m_editingDefinition), + CodegenHelper::makeSignatureString(declaration, m_newSignature, !m_editingDefinition)); } void AdaptSignatureAction::execute() @@ -175,7 +102,7 @@ DocumentChangeSet changes; KTextEditor::Range parameterRange = ClangIntegration::DUChainUtils::functionSignatureRange(otherSide); - QString newText = makeSignatureString(otherSide, m_newSignature, !m_editingDefinition); + QString newText = CodegenHelper::makeSignatureString(otherSide, m_newSignature, !m_editingDefinition); if (!m_editingDefinition) { // append a newline after the method signature in case the method definition follows newText += QLatin1Char('\n'); diff --git a/languages/clang/codegen/clangrefactoring.h b/languages/clang/codegen/clangrefactoring.h new file mode 100644 --- /dev/null +++ b/languages/clang/codegen/clangrefactoring.h @@ -0,0 +1,58 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 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) 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 . + * + */ + +#ifndef SIMPLEREFACTORING_H +#define SIMPLEREFACTORING_H + +#include "clangprivateexport.h" + +#include + +namespace KDevelop +{ +class Context; +class ContextMenuExtension; +class Declaration; +} + +class KDEVCLANGPRIVATE_EXPORT ClangRefactoring : public KDevelop::BasicRefactoring +{ + Q_OBJECT + +public: + explicit ClangRefactoring(QObject* parent = 0); + + void fillContextMenu(KDevelop::ContextMenuExtension& extension, KDevelop::Context* context) override; + + QString moveIntoSource(const KDevelop::IndexedDeclaration& iDecl); + + using KDevelop::BasicRefactoring::executeRenameAction; + +public slots: + void executeMoveIntoSourceAction(); + +private: + bool validCandidateToMoveIntoSource(KDevelop::Declaration* decl); +}; + +#endif diff --git a/languages/clang/codegen/clangrefactoring.cpp b/languages/clang/codegen/clangrefactoring.cpp new file mode 100644 --- /dev/null +++ b/languages/clang/codegen/clangrefactoring.cpp @@ -0,0 +1,231 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 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) 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 "clangrefactoring.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "duchain/clanghelpers.h" +#include "duchain/documentfinderhelpers.h" +#include "duchain/duchainutils.h" +#include "sourcemanipulation.h" +#include "util/clangdebug.h" + +using namespace KDevelop; + +ClangRefactoring::ClangRefactoring(QObject* parent) + : BasicRefactoring(parent) +{ + qRegisterMetaType(); +} + +void ClangRefactoring::fillContextMenu(ContextMenuExtension& extension, Context* context) +{ + auto declContext = dynamic_cast(context); + if (!declContext) { + return; + } + + DUChainReadLocker lock; + + auto declaration = declContext->declaration().data(); + if (!declaration) { + return; + } + + QFileInfo fileInfo(declaration->topContext()->url().str()); + if (!fileInfo.isWritable()) { + return; + } + + auto action = new QAction(i18n("Rename %1", declaration->qualifiedIdentifier().toString()), this); + action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); + action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); + connect(action, &QAction::triggered, this, &ClangRefactoring::executeRenameAction); + + extension.addAction(ContextMenuExtension::RefactorGroup, action); + + if (!validCandidateToMoveIntoSource(declaration)) { + return; + } + + action = new QAction( + i18n("Create separate definition for %1", declaration->qualifiedIdentifier().toString()), this); + action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); + connect(action, &QAction::triggered, this, &ClangRefactoring::executeMoveIntoSourceAction); + extension.addAction(ContextMenuExtension::RefactorGroup, action); +} + +bool ClangRefactoring::validCandidateToMoveIntoSource(Declaration* decl) +{ + if (!decl || !decl->isFunctionDeclaration() || !decl->type()) { + return false; + } + + if (!decl->internalContext() || decl->internalContext()->type() != DUContext::Function) { + return false; + } + + auto childCtx = decl->internalContext()->childContexts(); + if (childCtx.isEmpty()) { + return false; + } + + auto ctx = childCtx.first(); + if (!ctx || ctx->type() != DUContext::Other) { + return false; + } + + auto functionDecl = dynamic_cast(decl); + if (!functionDecl || functionDecl->isInline()) { + return false; + } + + return true; +} + +QString ClangRefactoring::moveIntoSource(const IndexedDeclaration& iDecl) +{ + DUChainReadLocker lock; + auto decl = iDecl.data(); + if (!decl) { + return i18n("No declaration under cursor"); + } + + const auto headerUrl = decl->url(); + auto targetUrl = DocumentFinderHelpers::sourceForHeader(headerUrl.str()); + + if (targetUrl.isEmpty() || targetUrl == headerUrl.str()) { + // TODO: Create source file if it doesn't exist + return i18n("No source file available for %1.", headerUrl.str()); + } + + lock.unlock(); + const IndexedString indexedTargetUrl(targetUrl); + auto top + = DUChain::self()->waitForUpdate(headerUrl, KDevelop::TopDUContext::AllDeclarationsAndContexts); + auto targetTopContext + = DUChain::self()->waitForUpdate(indexedTargetUrl, KDevelop::TopDUContext::AllDeclarationsAndContexts); + lock.lock(); + + if (!targetTopContext) { + return i18n("Failed to update DUChain for %1.", targetUrl); + } + + if (!top || !iDecl.data() || iDecl.data() != decl) { + return i18n("Declaration lost while updating."); + } + + clangDebug() << "moving" << decl->qualifiedIdentifier(); + + if (!validCandidateToMoveIntoSource(decl)) { + return i18n("Cannot create definition for this declaration."); + } + + auto otherCtx = decl->internalContext()->childContexts().first(); + + auto code = createCodeRepresentation(headerUrl); + if (!code) { + return i18n("No document for %1", headerUrl.str()); + } + + auto bodyRange = otherCtx->rangeInCurrentRevision(); + + auto prefixRange(ClangIntegration::DUChainUtils::functionSignatureRange(decl)); + const auto prefixText = code->rangeText(prefixRange); + for (int i = prefixText.length() - 1; i >= 0 && prefixText.at(i).isSpace(); --i) { + if (bodyRange.start().column() == 0) { + bodyRange.setStart(bodyRange.start() - KTextEditor::Cursor(1, 0)); + if (bodyRange.start().line() == prefixRange.start().line()) { + bodyRange.setStart(KTextEditor::Cursor(bodyRange.start().line(), prefixRange.start().column() + i)); + } else { + int lastNewline = prefixText.lastIndexOf(QLatin1Char('\n'), i - 1); + bodyRange.setStart(KTextEditor::Cursor(bodyRange.start().line(), i - lastNewline - 1)); + } + } else { + bodyRange.setStart(bodyRange.start() - KTextEditor::Cursor(0, 1)); + } + } + + const QString body = code->rangeText(bodyRange); + SourceCodeInsertion ins(targetTopContext); + auto parentId = decl->internalContext()->parentContext()->scopeIdentifier(false); + + ins.setSubScope(parentId); + + Identifier id(IndexedString(decl->qualifiedIdentifier().mid(parentId.count()).toString())); + clangDebug() << "id:" << id; + + if (!ins.insertFunctionDeclaration(decl, id, body)) { + return i18n("Insertion failed"); + } + lock.unlock(); + + auto applied = ins.changes().applyAllChanges(); + if (!applied) { + return i18n("Applying changes failed: %1", applied.m_failureReason); + } + + // replace function body with a semicolon + DocumentChangeSet changeHeader; + changeHeader.addChange(DocumentChange(headerUrl, bodyRange, body, QStringLiteral(";"))); + applied = changeHeader.applyAllChanges(); + if (!applied) { + return i18n("Applying changes failed: %1", applied.m_failureReason); + } + + ICore::self()->languageController()->backgroundParser()->addDocument(headerUrl); + ICore::self()->languageController()->backgroundParser()->addDocument(indexedTargetUrl); + + return {}; +} + +void ClangRefactoring::executeMoveIntoSourceAction() +{ + auto action = qobject_cast(sender()); + Q_ASSERT(action); + + auto iDecl = action->data().value(); + if (!iDecl.isValid()) { + iDecl = declarationUnderCursor(false); + } + + const auto error = moveIntoSource(iDecl); + if (!error.isEmpty()) { + KMessageBox::error(nullptr, error); + } +} diff --git a/languages/clang/codegen/codegenhelper.h b/languages/clang/codegen/codegenhelper.h --- a/languages/clang/codegen/codegenhelper.h +++ b/languages/clang/codegen/codegenhelper.h @@ -23,6 +23,8 @@ #include #include +struct Signature; + namespace CodegenHelper { ///Returns the type that should be used for shortened printing of the same. KDevelop::AbstractType::Ptr typeForShortenedString(KDevelop::Declaration* decl); @@ -39,6 +41,8 @@ ///Returns a simplified string version of the given type: Template default-parameters are stripped away, qualified identifiers are simplified so they are as short as possible, while staying visible from the given context. QString simplifiedTypeString(const KDevelop::AbstractType::Ptr& type, KDevelop::DUContext* visibilityFrom); + +QString makeSignatureString(const KDevelop::Declaration* functionDecl, const Signature& signature, const bool editingDefinition); }; #endif // CODEGENHELPER_H diff --git a/languages/clang/codegen/codegenhelper.cpp b/languages/clang/codegen/codegenhelper.cpp --- a/languages/clang/codegen/codegenhelper.cpp +++ b/languages/clang/codegen/codegenhelper.cpp @@ -18,6 +18,10 @@ #include "codegenhelper.h" +#include "adaptsignatureaction.h" + +#include +#include #include #include #include @@ -103,24 +107,14 @@ return maxPointerLevel; } - IdentifiedType* idType = dynamic_cast(type.data()); - if (idType) { - Declaration* decl = idType->declaration(top); - if (decl) { - id.setIdentifier(decl->qualifiedIdentifier()); - } else { - id.setIdentifier(idType->qualifiedIdentifier()); - } - } else { - //Just create it as an expression - AbstractType::Ptr useTypeText = type; - if (type->modifiers() & AbstractType::ConstModifier) { - //Remove the 'const' modifier, as it will be added to the type-identifier below - useTypeText = type->indexed().abstractType(); - useTypeText->setModifiers(useTypeText->modifiers() & (~AbstractType::ConstModifier)); - } - id.setIdentifier(QualifiedIdentifier(useTypeText->toString(), true)); + AbstractType::Ptr useTypeText = type; + if (type->modifiers() & AbstractType::ConstModifier) { + //Remove the 'const' modifier, as it will be added to the type-identifier below + useTypeText = type->indexed().abstractType(); + useTypeText->setModifiers(useTypeText->modifiers() & (~AbstractType::ConstModifier)); } + id.setIdentifier(QualifiedIdentifier(useTypeText->toString(), true)); + if (type->modifiers() & AbstractType::ConstModifier) { id.setIsConstant(true); } @@ -384,9 +378,6 @@ IndexedTypeIdentifier identifier = identifierForType(type, ctx ? ctx->topContext() : 0); - if (type.cast()) { - identifier = type.cast()->identifier(); - } identifier = stripPrefixIdentifiers(identifier, stripPrefix); if (isReference) { @@ -405,4 +396,74 @@ return identifier; } +QString makeSignatureString(const KDevelop::Declaration* functionDecl, const Signature& signature, const bool editingDefinition) +{ + if (!functionDecl || !functionDecl->internalContext()) { + return {}; + } + const auto visibilityFrom = functionDecl->internalContext()->parentContext(); + if (!visibilityFrom) { + return {}; + } + + QString ret; + + if (!editingDefinition) { + auto classMember = dynamic_cast(functionDecl); + if (classMember && classMember->isStatic()) { + ret += QLatin1String("static "); + } + } + + // constructors don't have a return type + if (signature.returnType.isValid()) { + ret += CodegenHelper::simplifiedTypeString(signature.returnType.abstractType(), + visibilityFrom); + ret += QLatin1Char(' '); + } + + ret += editingDefinition ? functionDecl->qualifiedIdentifier().toString() : functionDecl->identifier().toString(); + + ret += QLatin1Char('('); + int pos = 0; + + foreach(const ParameterItem &item, signature.parameters) + { + if (pos != 0) { + ret += QLatin1String(", "); + } + + AbstractType::Ptr type = item.first.abstractType(); + + QString arrayAppendix; + ArrayType::Ptr arrayType; + while ((arrayType = type.cast())) { + type = arrayType->elementType(); + //note: we have to prepend since we iterate from outside, i.e. from right to left. + if (arrayType->dimension()) { + arrayAppendix.prepend(QStringLiteral("[%1]").arg(arrayType->dimension())); + } else { + // dimensionless + arrayAppendix.prepend(QLatin1String("[]")); + } + } + ret += CodegenHelper::simplifiedTypeString(type, + visibilityFrom); + + if (!item.second.isEmpty()) { + ret += QLatin1Char(' ') + item.second; + } + ret += arrayAppendix; + if (signature.defaultParams.size() > pos && !signature.defaultParams[pos].isEmpty()) { + ret += QLatin1String(" = ") + signature.defaultParams[pos]; + } + ++pos; + } + ret += QLatin1Char(')'); + if (signature.isConst) { + ret += QLatin1String(" const"); + } + return ret; +} + } diff --git a/languages/clang/codegen/sourcemanipulation.h b/languages/clang/codegen/sourcemanipulation.h new file mode 100644 --- /dev/null +++ b/languages/clang/codegen/sourcemanipulation.h @@ -0,0 +1,73 @@ +/* + * This file is part of KDevelop + * + * Copyright 2009 David Nolden + * Copyright 2015 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) 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 . + * + */ + +#ifndef CLANG_SOURCEMANIPULATION_H +#define CLANG_SOURCEMANIPULATION_H + +#include +#include +#include + +class SourceCodeInsertion : public QSharedData +{ +public: + explicit SourceCodeInsertion(KDevelop::TopDUContext* topContext); + ~SourceCodeInsertion(); + + /// Set optional sub-scope into which the code should be inserted, under 'context' + void setSubScope(const KDevelop::QualifiedIdentifier& scope); + + /// @param body function-body, including parens + bool insertFunctionDeclaration(KDevelop::Declaration* decl, const KDevelop::Identifier& id, const QString& body = QString()); + + KDevelop::DocumentChangeSet changes(); + +private: + + /// Returns the exact position where the item should be inserted so it is in the given line. + /// The inserted item has to start with a newline, and does not need to end with a newline. + KTextEditor::Range insertionRange(int line); + + /// Returns a line for inserting the given declaration + int findInsertionPoint() const; + + // Should apply m_scope to the given declaration string + QString applySubScope(const QString& decl) const; + + QString indentation() const; + QString applyIndentation(const QString& decl) const; + + KTextEditor::Cursor end() const; + +private: + KDevelop::DocumentChangeSet m_changeSet; + KDevelop::DUContextPointer m_context; + KDevelop::QualifiedIdentifier m_scope; + KDevelop::TopDUContextPointer m_topContext; + // Represents the whole code of the manipulated top-context for reading. + // Must be checked for zero before using. It is zero if the file could not be read. + const KDevelop::CodeRepresentation::Ptr m_codeRepresentation; +}; + +#endif // CLANG_SOURCEMANIPULATION_H diff --git a/languages/clang/codegen/sourcemanipulation.cpp b/languages/clang/codegen/sourcemanipulation.cpp new file mode 100644 --- /dev/null +++ b/languages/clang/codegen/sourcemanipulation.cpp @@ -0,0 +1,287 @@ +/* + * This file is part of KDevelop + * + * Copyright 2009 David Nolden + * Copyright 2015 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) 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 "sourcemanipulation.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "codegenhelper.h" +#include "adaptsignatureaction.h" +#include "util/clangdebug.h" + +using namespace KDevelop; + +namespace +{ +QualifiedIdentifier stripPrefixes(const DUContextPointer& ctx, const QualifiedIdentifier& id) +{ + if (!ctx) { + return id; + } + + auto imports = ctx->fullyApplyAliases({}, ctx->topContext()); + if (imports.contains(id)) { + return {}; /// The id is a namespace that is imported into the current context + } + + auto basicDecls = ctx->findDeclarations(id, CursorInRevision::invalid(), {}, nullptr, + (DUContext::SearchFlags)(DUContext::NoSelfLookUp | DUContext::NoFiltering)); + + if (basicDecls.isEmpty()) { + return id; + } + + auto newId = id.mid(1); + auto result = id; + while (!newId.isEmpty()) { + auto foundDecls + = ctx->findDeclarations(newId, CursorInRevision::invalid(), {}, nullptr, + (DUContext::SearchFlags)(DUContext::NoSelfLookUp | DUContext::NoFiltering)); + + if (foundDecls == basicDecls) { + result = newId; // must continue to find the shortest possible identifier + // esp. for cases where nested namespaces are used (e.g. using namespace a::b::c;) + newId = newId.mid(1); + } + } + + return result; +} + +// Re-indents the code so the leftmost line starts at zero +QString zeroIndentation(const QString& str, int fromLine = 0) +{ + QStringList lines = str.split(QLatin1Char('\n')); + QStringList ret; + + if (fromLine < lines.size()) { + ret = lines.mid(0, fromLine); + lines = lines.mid(fromLine); + } + + QRegExp nonWhiteSpace(QStringLiteral("\\S")); + int minLineStart = 10000; + foreach (const auto& line, lines) { + int lineStart = line.indexOf(nonWhiteSpace); + if (lineStart < minLineStart) { + minLineStart = lineStart; + } + } + + foreach (const auto& line, lines) { + ret << line.mid(minLineStart); + } + + return ret.join(QStringLiteral("\n")); +} +} + +DocumentChangeSet SourceCodeInsertion::changes() +{ + return m_changeSet; +} + +void SourceCodeInsertion::setSubScope(const QualifiedIdentifier& scope) +{ + m_scope = scope; + + if (!m_context) { + return; + } + + QStringList needNamespace = m_scope.toStringList(); + + bool foundChild = true; + while (!needNamespace.isEmpty() && foundChild) { + foundChild = false; + + foreach (DUContext* child, m_context->childContexts()) { + clangDebug() << "checking child" << child->localScopeIdentifier().toString() << "against" + << needNamespace.first(); + if (child->localScopeIdentifier().toString() == needNamespace.first() && child->type() == DUContext::Namespace) { + clangDebug() << "taking"; + m_context = child; + foundChild = true; + needNamespace.pop_front(); + break; + } + } + } + + m_scope = stripPrefixes(m_context, QualifiedIdentifier(needNamespace.join(QStringLiteral("::")))); +} + +QString SourceCodeInsertion::applySubScope(const QString& decl) const +{ + if (m_scope.isEmpty()) { + return decl; + } + + QString scopeType = QStringLiteral("namespace"); + QString scopeClose; + + if (m_context && m_context->type() == DUContext::Class) { + scopeType = QStringLiteral("struct"); + scopeClose = QStringLiteral(";"); + } + + QString ret; + foreach (const QString& scope, m_scope.toStringList()) { + ret += scopeType + QStringLiteral(" ") + scope + QStringLiteral(" {\n"); + } + + ret += decl; + ret += QStringLiteral("}") + scopeClose + QStringLiteral("\n").repeated(m_scope.count()); + + return ret; +} + +SourceCodeInsertion::SourceCodeInsertion(TopDUContext* topContext) + : m_context(topContext) + , m_topContext(topContext) + , m_codeRepresentation(createCodeRepresentation(m_topContext->url())) +{ +} + +SourceCodeInsertion::~SourceCodeInsertion() +{ +} + +KTextEditor::Cursor SourceCodeInsertion::end() const +{ + auto ret = m_context->rangeInCurrentRevision().end(); + if (m_codeRepresentation && m_codeRepresentation->lines() && dynamic_cast(m_context.data())) { + ret.setLine(m_codeRepresentation->lines() - 1); + ret.setColumn(m_codeRepresentation->line(ret.line()).size()); + } + return ret; +} + +KTextEditor::Range SourceCodeInsertion::insertionRange(int line) +{ + if (line == 0 || !m_codeRepresentation) { + return KTextEditor::Range(line, 0, line, 0); + } + + KTextEditor::Range range(line - 1, m_codeRepresentation->line(line - 1).size(), line - 1, + m_codeRepresentation->line(line - 1).size()); + // If the context finishes on that line, then this will need adjusting + if (!m_context->rangeInCurrentRevision().contains(range)) { + range.start() = m_context->rangeInCurrentRevision().end(); + if (range.start().column() > 0) { + range.start() = range.start() - KTextEditor::Cursor(0, 1); + } + range.end() = range.start(); + } + + return range; +} + +bool SourceCodeInsertion::insertFunctionDeclaration(KDevelop::Declaration* declaration, const Identifier& id, const QString& body) +{ + if (!m_context) { + return false; + } + + Signature signature; + const auto localDeclarations = declaration->internalContext()->localDeclarations(); + signature.parameters.reserve(localDeclarations.count()); + std::transform(localDeclarations.begin(), localDeclarations.end(), + std::back_inserter(signature.parameters), + [] (Declaration* argument) -> ParameterItem + { return {IndexedType(argument->indexedType()), argument->identifier().toString()}; }); + + auto funcType = declaration->type(); + auto returnType = funcType->returnType(); + if (auto classFunDecl = dynamic_cast(declaration)) { + if (classFunDecl->isConstructor() || classFunDecl->isDestructor()) { + returnType = nullptr; + } + } + signature.returnType = returnType->indexed(); + signature.isConst = funcType->modifiers() & AbstractType::ConstModifier; + + QString decl = CodegenHelper::makeSignatureString(declaration, signature, true); + decl.replace(declaration->qualifiedIdentifier().toString(), id.toString()); + + if (body.isEmpty()) { + decl += QStringLiteral(";"); + } else { + if (!body.startsWith(QLatin1Char(' ')) && !body.startsWith(QLatin1Char('\n'))) { + decl += QStringLiteral(" "); + } + decl += zeroIndentation(body); + } + + int line = findInsertionPoint(); + + decl = QStringLiteral("\n\n") + applySubScope(decl); + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFile(declaration->url().str()); + auto i = ICore::self()->sourceFormatterController()->formatterForMimeType(mime); + if (i) { + decl = i->formatSource(decl, declaration->url().toUrl(), mime); + } + + return m_changeSet.addChange(DocumentChange(m_context->url(), insertionRange(line), QString(), decl)); +} + +int SourceCodeInsertion::findInsertionPoint() const +{ + int line = end().line(); + + foreach (auto decl, m_context->localDeclarations()) { + if (m_context->type() == DUContext::Class) { + continue; + } + + if (!dynamic_cast(decl)) { + continue; + } + + line = decl->range().end.line + 1; + if (decl->internalContext()) { + line = decl->internalContext()->range().end.line + 1; + } + } + + clangDebug() << line << m_context->scopeIdentifier(true) << m_context->rangeInCurrentRevision() + << m_context->url().toUrl() << m_context->parentContext(); + clangDebug() << "count of declarations:" << m_context->topContext()->localDeclarations().size(); + + return line; +} diff --git a/languages/clang/duchain/builder.cpp b/languages/clang/duchain/builder.cpp --- a/languages/clang/duchain/builder.cpp +++ b/languages/clang/duchain/builder.cpp @@ -560,8 +560,8 @@ AbstractType *createType(CXType type, CXCursor parent) { auto numTA = clang_Type_getNumTemplateArguments(type); - if (numTA != -1) { - // This is a class template specialization. + // TODO: We should really expose more types to libclang! + if (numTA != -1 && ClangString(clang_getTypeSpelling(type)).toString().contains(QLatin1Char('<'))) { return createClassTemplateSpecializationType(type); } diff --git a/languages/clang/duchain/documentfinderhelpers.h b/languages/clang/duchain/documentfinderhelpers.h --- a/languages/clang/duchain/documentfinderhelpers.h +++ b/languages/clang/duchain/documentfinderhelpers.h @@ -47,6 +47,13 @@ /// @see KDevelop::IBuddyDocumentFinder KDEVCLANGPRIVATE_EXPORT QVector< QUrl > getPotentialBuddies(const QUrl &url, bool checkDUChain = true); + +/** + * Returns path to the source file for given @p headerPath + * + * If no source file exists or @p headerPath is not a header an empty sting is returned + */ +KDEVCLANGPRIVATE_EXPORT QString sourceForHeader(const QString& headerPath); }; #endif // DOCUMENTFINDERHELPERS_H diff --git a/languages/clang/duchain/documentfinderhelpers.cpp b/languages/clang/duchain/documentfinderhelpers.cpp --- a/languages/clang/duchain/documentfinderhelpers.cpp +++ b/languages/clang/duchain/documentfinderhelpers.cpp @@ -265,4 +265,23 @@ return buddies; } +QString sourceForHeader(const QString& headerPath) +{ + if (!ClangHelpers::isHeader(headerPath)) { + return {}; + } + + QString targetUrl; + auto buddies = DocumentFinderHelpers::getPotentialBuddies(QUrl::fromLocalFile(headerPath)); + for (const auto& buddy : buddies) { + const auto local = buddy.toLocalFile(); + if (QFileInfo::exists(local)) { + targetUrl = local; + break; + } + } + + return targetUrl; +} + } diff --git a/languages/clang/tests/test_assistants.h b/languages/clang/tests/test_assistants.h --- a/languages/clang/tests/test_assistants.h +++ b/languages/clang/tests/test_assistants.h @@ -36,6 +36,9 @@ void testSignatureAssistant(); void testUnknownDeclarationAssistant_data(); void testUnknownDeclarationAssistant(); + + void testMoveIntoSource_data(); + void testMoveIntoSource(); }; #endif diff --git a/languages/clang/tests/test_assistants.cpp b/languages/clang/tests/test_assistants.cpp --- a/languages/clang/tests/test_assistants.cpp +++ b/languages/clang/tests/test_assistants.cpp @@ -19,14 +19,17 @@ #include "test_assistants.h" +#include "codegen/clangrefactoring.h" + #include #include #include #include #include #include +#include #include @@ -40,6 +43,7 @@ #include #include #include +#include #include #include @@ -532,3 +536,155 @@ 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); + + 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"); +}