forked from qt-creator/qt-creator
CppEditor: Let users create implementations for all member functions
... in one go. Fixes: QTCREATORBUG-12164 Change-Id: Ifc81c8b1caf4319ce57882375f513d72e4c0ea52 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -145,6 +145,8 @@ private slots:
|
|||||||
void test_quickfix_InsertDefFromDecl_templateClass();
|
void test_quickfix_InsertDefFromDecl_templateClass();
|
||||||
void test_quickfix_InsertDefFromDecl_templateFunction();
|
void test_quickfix_InsertDefFromDecl_templateFunction();
|
||||||
void test_quickfix_InsertDefFromDecl_notTriggeredForFriendFunc();
|
void test_quickfix_InsertDefFromDecl_notTriggeredForFriendFunc();
|
||||||
|
void test_quickfix_InsertDefsFromDecls_data();
|
||||||
|
void test_quickfix_InsertDefsFromDecls();
|
||||||
|
|
||||||
void test_quickfix_InsertDeclFromDef();
|
void test_quickfix_InsertDeclFromDef();
|
||||||
void test_quickfix_InsertDeclFromDef_templateFuncTypename();
|
void test_quickfix_InsertDeclFromDef_templateFuncTypename();
|
||||||
|
|||||||
@@ -254,6 +254,10 @@ QuickFixOperationTest::QuickFixOperationTest(const QList<QuickFixTestDocument::P
|
|||||||
removeTrailingWhitespace(result);
|
removeTrailingWhitespace(result);
|
||||||
if (!expectedFailMessage.isEmpty())
|
if (!expectedFailMessage.isEmpty())
|
||||||
QEXPECT_FAIL("", expectedFailMessage.data(), Continue);
|
QEXPECT_FAIL("", expectedFailMessage.data(), Continue);
|
||||||
|
else if (result != testDocument->m_expectedSource) {
|
||||||
|
qDebug() << "---" << testDocument->m_expectedSource;
|
||||||
|
qDebug() << "+++" << result;
|
||||||
|
}
|
||||||
QCOMPARE(result, testDocument->m_expectedSource);
|
QCOMPARE(result, testDocument->m_expectedSource);
|
||||||
|
|
||||||
// Undo the change
|
// Undo the change
|
||||||
@@ -3485,6 +3489,133 @@ void CppEditorPlugin::test_quickfix_InsertDefFromDecl_notTriggeredForFriendFunc(
|
|||||||
QuickFixOperationTest(singleDocument(contents, ""), &factory);
|
QuickFixOperationTest(singleDocument(contents, ""), &factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CppEditorPlugin::test_quickfix_InsertDefsFromDecls_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QByteArrayList>("headers");
|
||||||
|
QTest::addColumn<QByteArrayList>("sources");
|
||||||
|
QTest::addColumn<int>("mode");
|
||||||
|
|
||||||
|
QByteArray origHeader = R"(
|
||||||
|
namespace N {
|
||||||
|
class @C
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
friend void ignoredFriend();
|
||||||
|
void ignoredImplemented() {};
|
||||||
|
void ignoredImplemented2(); // Below
|
||||||
|
void ignoredImplemented3(); // In cpp file
|
||||||
|
void funcNotSelected();
|
||||||
|
void funcInline();
|
||||||
|
void funcBelow();
|
||||||
|
void funcCppFile();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void ignoredSignal();
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void C::ignoredImplemented2() {}
|
||||||
|
|
||||||
|
} // namespace N)";
|
||||||
|
QByteArray origSource = R"(
|
||||||
|
#include "file.h"
|
||||||
|
|
||||||
|
namespace N {
|
||||||
|
|
||||||
|
void C::ignoredImplemented3() {}
|
||||||
|
|
||||||
|
} // namespace N)";
|
||||||
|
|
||||||
|
QByteArray expectedHeader = R"(
|
||||||
|
namespace N {
|
||||||
|
class C
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
friend void ignoredFriend();
|
||||||
|
void ignoredImplemented() {};
|
||||||
|
void ignoredImplemented2(); // Below
|
||||||
|
void ignoredImplemented3(); // In cpp file
|
||||||
|
void funcNotSelected();
|
||||||
|
void funcInline()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
void funcBelow();
|
||||||
|
void funcCppFile();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void ignoredSignal();
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void C::ignoredImplemented2() {}
|
||||||
|
|
||||||
|
inline void C::funcBelow()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace N)";
|
||||||
|
QByteArray expectedSource = R"(
|
||||||
|
#include "file.h"
|
||||||
|
|
||||||
|
namespace N {
|
||||||
|
|
||||||
|
void C::ignoredImplemented3() {}
|
||||||
|
|
||||||
|
void C::funcCppFile()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace N)";
|
||||||
|
QTest::addRow("normal case")
|
||||||
|
<< QByteArrayList{origHeader, expectedHeader}
|
||||||
|
<< QByteArrayList{origSource, expectedSource}
|
||||||
|
<< int(InsertDefsFromDecls::Mode::Alternating);
|
||||||
|
QTest::addRow("aborted dialog")
|
||||||
|
<< QByteArrayList{origHeader, origHeader}
|
||||||
|
<< QByteArrayList{origSource, origSource}
|
||||||
|
<< int(InsertDefsFromDecls::Mode::Off);
|
||||||
|
|
||||||
|
origHeader = R"(
|
||||||
|
namespace N {
|
||||||
|
class @C
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
friend void ignoredFriend();
|
||||||
|
void ignoredImplemented() {};
|
||||||
|
void ignoredImplemented2(); // Below
|
||||||
|
void ignoredImplemented3(); // In cpp file
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void ignoredSignal();
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void C::ignoredImplemented2() {}
|
||||||
|
|
||||||
|
} // namespace N)";
|
||||||
|
QTest::addRow("no candidates")
|
||||||
|
<< QByteArrayList{origHeader, ""}
|
||||||
|
<< QByteArrayList{origSource, ""}
|
||||||
|
<< int(InsertDefsFromDecls::Mode::Alternating);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CppEditorPlugin::test_quickfix_InsertDefsFromDecls()
|
||||||
|
{
|
||||||
|
QFETCH(QByteArrayList, headers);
|
||||||
|
QFETCH(QByteArrayList, sources);
|
||||||
|
QFETCH(int, mode);
|
||||||
|
|
||||||
|
QList<QuickFixTestDocument::Ptr> testDocuments({
|
||||||
|
QuickFixTestDocument::create("file.h", headers.at(0), headers.at(1)),
|
||||||
|
QuickFixTestDocument::create("file.cpp", sources.at(0), sources.at(1))});
|
||||||
|
InsertDefsFromDecls factory;
|
||||||
|
factory.setMode(static_cast<InsertDefsFromDecls::Mode>(mode));
|
||||||
|
QuickFixOperationTest(testDocuments, &factory);
|
||||||
|
}
|
||||||
|
|
||||||
// Function for one of InsertDeclDef section cases
|
// Function for one of InsertDeclDef section cases
|
||||||
void insertToSectionDeclFromDef(const QByteArray §ion, int sectionIndex)
|
void insertToSectionDeclFromDef(const QByteArray §ion, int sectionIndex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -73,7 +73,10 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QHash>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
|
#include <QPair>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
@@ -2991,95 +2994,124 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void perform() override
|
static void insertDefinition(
|
||||||
|
const CppQuickFixOperation *op,
|
||||||
|
InsertionLocation loc,
|
||||||
|
DefPos defPos,
|
||||||
|
DeclaratorAST *declAST,
|
||||||
|
Declaration *decl,
|
||||||
|
const QString &targetFilePath,
|
||||||
|
ChangeSet *changeSet = nullptr,
|
||||||
|
QList<ChangeSet::Range> *indentRanges = nullptr)
|
||||||
{
|
{
|
||||||
CppRefactoringChanges refactoring(snapshot());
|
CppRefactoringChanges refactoring(op->snapshot());
|
||||||
if (!m_loc.isValid())
|
if (!loc.isValid())
|
||||||
m_loc = insertLocationForMethodDefinition(m_decl, true, NamespaceHandling::Ignore,
|
loc = insertLocationForMethodDefinition(decl, true, NamespaceHandling::Ignore,
|
||||||
refactoring, m_targetFileName);
|
refactoring, targetFilePath);
|
||||||
QTC_ASSERT(m_loc.isValid(), return);
|
QTC_ASSERT(loc.isValid(), return);
|
||||||
|
|
||||||
CppRefactoringFilePtr targetFile = refactoring.file(m_loc.fileName());
|
CppRefactoringFilePtr targetFile = refactoring.file(loc.fileName());
|
||||||
Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
|
Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
|
||||||
oo.showFunctionSignatures = true;
|
oo.showFunctionSignatures = true;
|
||||||
oo.showReturnTypes = true;
|
oo.showReturnTypes = true;
|
||||||
oo.showArgumentNames = true;
|
oo.showArgumentNames = true;
|
||||||
oo.showEnclosingTemplate = true;
|
oo.showEnclosingTemplate = true;
|
||||||
|
|
||||||
if (m_defpos == DefPosInsideClass) {
|
if (defPos == DefPosInsideClass) {
|
||||||
const int targetPos = targetFile->position(m_loc.line(), m_loc.column());
|
const int targetPos = targetFile->position(loc.line(), loc.column());
|
||||||
ChangeSet target;
|
ChangeSet localChangeSet;
|
||||||
target.replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';'
|
ChangeSet * const target = changeSet ? changeSet : &localChangeSet;
|
||||||
targetFile->setChangeSet(target);
|
target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';'
|
||||||
targetFile->appendIndentRange(ChangeSet::Range(targetPos, targetPos + 4));
|
const ChangeSet::Range indentRange(targetPos, targetPos + 4);
|
||||||
targetFile->setOpenEditor(true, targetPos);
|
if (indentRanges)
|
||||||
targetFile->apply();
|
indentRanges->append(indentRange);
|
||||||
|
else
|
||||||
|
targetFile->appendIndentRange(indentRange);
|
||||||
|
|
||||||
// Move cursor inside definition
|
if (!changeSet) {
|
||||||
QTextCursor c = targetFile->cursor();
|
targetFile->setChangeSet(*target);
|
||||||
c.setPosition(targetPos);
|
targetFile->setOpenEditor(true, targetPos);
|
||||||
c.movePosition(QTextCursor::Down);
|
targetFile->apply();
|
||||||
c.movePosition(QTextCursor::EndOfLine);
|
|
||||||
editor()->setTextCursor(c);
|
// Move cursor inside definition
|
||||||
|
QTextCursor c = targetFile->cursor();
|
||||||
|
c.setPosition(targetPos);
|
||||||
|
c.movePosition(QTextCursor::Down);
|
||||||
|
c.movePosition(QTextCursor::EndOfLine);
|
||||||
|
op->editor()->setTextCursor(c);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// make target lookup context
|
// make target lookup context
|
||||||
Document::Ptr targetDoc = targetFile->cppDocument();
|
Document::Ptr targetDoc = targetFile->cppDocument();
|
||||||
Scope *targetScope = targetDoc->scopeAt(m_loc.line(), m_loc.column());
|
Scope *targetScope = targetDoc->scopeAt(loc.line(), loc.column());
|
||||||
LookupContext targetContext(targetDoc, snapshot());
|
LookupContext targetContext(targetDoc, op->snapshot());
|
||||||
ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope);
|
ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope);
|
||||||
if (!targetCoN)
|
if (!targetCoN)
|
||||||
targetCoN = targetContext.globalNamespace();
|
targetCoN = targetContext.globalNamespace();
|
||||||
|
|
||||||
// setup rewriting to get minimally qualified names
|
// setup rewriting to get minimally qualified names
|
||||||
SubstitutionEnvironment env;
|
SubstitutionEnvironment env;
|
||||||
env.setContext(context());
|
env.setContext(op->context());
|
||||||
env.switchScope(m_decl->enclosingScope());
|
env.switchScope(decl->enclosingScope());
|
||||||
UseMinimalNames q(targetCoN);
|
UseMinimalNames q(targetCoN);
|
||||||
env.enter(&q);
|
env.enter(&q);
|
||||||
Control *control = context().bindings()->control().data();
|
Control *control = op->context().bindings()->control().data();
|
||||||
|
|
||||||
// rewrite the function type
|
// rewrite the function type
|
||||||
const FullySpecifiedType tn = rewriteType(m_decl->type(), &env, control);
|
const FullySpecifiedType tn = rewriteType(decl->type(), &env, control);
|
||||||
|
|
||||||
// rewrite the function name
|
// rewrite the function name
|
||||||
if (nameIncludesOperatorName(m_decl->name())) {
|
if (nameIncludesOperatorName(decl->name())) {
|
||||||
CppRefactoringFilePtr file = refactoring.file(fileName());
|
CppRefactoringFilePtr file = refactoring.file(op->fileName());
|
||||||
const QString operatorNameText = file->textOf(m_declAST->core_declarator);
|
const QString operatorNameText = file->textOf(declAST->core_declarator);
|
||||||
oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' '));
|
oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' '));
|
||||||
}
|
}
|
||||||
const QString name = oo.prettyName(LookupContext::minimalName(m_decl, targetCoN,
|
const QString name = oo.prettyName(LookupContext::minimalName(decl, targetCoN,
|
||||||
control));
|
control));
|
||||||
const QString defText = inlinePrefix(
|
const QString defText = inlinePrefix(
|
||||||
m_targetFileName, [this] { return m_defpos == DefPosOutsideClass; })
|
targetFilePath, [defPos] { return defPos == DefPosOutsideClass; })
|
||||||
+ oo.prettyType(tn, name)
|
+ oo.prettyType(tn, name)
|
||||||
+ QLatin1String("\n{\n\n}");
|
+ QLatin1String("\n{\n\n}");
|
||||||
|
|
||||||
const int targetPos = targetFile->position(m_loc.line(), m_loc.column());
|
const int targetPos = targetFile->position(loc.line(), loc.column());
|
||||||
const int targetPos2 = qMax(0, targetFile->position(m_loc.line(), 1) - 1);
|
const int targetPos2 = qMax(0, targetFile->position(loc.line(), 1) - 1);
|
||||||
|
|
||||||
ChangeSet target;
|
ChangeSet localChangeSet;
|
||||||
target.insert(targetPos, m_loc.prefix() + defText + m_loc.suffix());
|
ChangeSet * const target = changeSet ? changeSet : &localChangeSet;
|
||||||
targetFile->setChangeSet(target);
|
target->insert(targetPos, loc.prefix() + defText + loc.suffix());
|
||||||
targetFile->appendIndentRange(ChangeSet::Range(targetPos2, targetPos));
|
const ChangeSet::Range indentRange(targetPos2, targetPos);
|
||||||
targetFile->setOpenEditor(true, targetPos);
|
if (indentRanges)
|
||||||
targetFile->apply();
|
indentRanges->append(indentRange);
|
||||||
|
else
|
||||||
|
targetFile->appendIndentRange(indentRange);
|
||||||
|
|
||||||
// Move cursor inside definition
|
if (!changeSet) {
|
||||||
QTextCursor c = targetFile->cursor();
|
targetFile->setChangeSet(*target);
|
||||||
c.setPosition(targetPos);
|
targetFile->setOpenEditor(true, targetPos);
|
||||||
c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,
|
targetFile->apply();
|
||||||
m_loc.prefix().count(QLatin1String("\n")) + 2);
|
|
||||||
c.movePosition(QTextCursor::EndOfLine);
|
// Move cursor inside definition
|
||||||
if (m_defpos == DefPosImplementationFile) {
|
QTextCursor c = targetFile->cursor();
|
||||||
if (targetFile->editor())
|
c.setPosition(targetPos);
|
||||||
targetFile->editor()->setTextCursor(c);
|
c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,
|
||||||
} else {
|
loc.prefix().count(QLatin1String("\n")) + 2);
|
||||||
editor()->setTextCursor(c);
|
c.movePosition(QTextCursor::EndOfLine);
|
||||||
|
if (defPos == DefPosImplementationFile) {
|
||||||
|
if (targetFile->editor())
|
||||||
|
targetFile->editor()->setTextCursor(c);
|
||||||
|
} else {
|
||||||
|
op->editor()->setTextCursor(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void perform() override
|
||||||
|
{
|
||||||
|
insertDefinition(this, m_loc, m_defpos, m_declAST, m_decl, m_targetFileName);
|
||||||
|
}
|
||||||
|
|
||||||
Declaration *m_decl;
|
Declaration *m_decl;
|
||||||
DeclaratorAST *m_declAST;
|
DeclaratorAST *m_declAST;
|
||||||
InsertionLocation m_loc;
|
InsertionLocation m_loc;
|
||||||
@@ -3327,6 +3359,250 @@ QString InsertMemberFromInitialization::getType(
|
|||||||
return tpp(type);
|
return tpp(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MemberFunctionImplSetting
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Symbol *func = nullptr;
|
||||||
|
DefPos defPos = DefPosImplementationFile;
|
||||||
|
};
|
||||||
|
using MemberFunctionImplSettings = QList<MemberFunctionImplSetting>;
|
||||||
|
|
||||||
|
class AddImplementationsDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_DECLARE_TR_FUNCTIONS(AddImplementationsDialog)
|
||||||
|
public:
|
||||||
|
AddImplementationsDialog(const QList<Symbol *> &candidates, const Utils::FilePath &implFile)
|
||||||
|
: QDialog(Core::ICore::dialogParent()), m_candidates(candidates)
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Member Function Implementations"));
|
||||||
|
|
||||||
|
const auto defaultImplTargetComboBox = new QComboBox;
|
||||||
|
QStringList implTargetStrings{tr("None"), tr("Inline"), tr("Outside Class")};
|
||||||
|
if (!implFile.isEmpty())
|
||||||
|
implTargetStrings.append(implFile.fileName());
|
||||||
|
defaultImplTargetComboBox->insertItems(0, implTargetStrings);
|
||||||
|
connect(defaultImplTargetComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
|
[this](int index) {
|
||||||
|
for (QComboBox * const cb : m_implTargetBoxes)
|
||||||
|
cb->setCurrentIndex(index);
|
||||||
|
});
|
||||||
|
const auto defaultImplTargetLayout = new QHBoxLayout;
|
||||||
|
defaultImplTargetLayout->addWidget(new QLabel(tr("Default Implementation Location:")));
|
||||||
|
defaultImplTargetLayout->addWidget(defaultImplTargetComboBox);
|
||||||
|
|
||||||
|
const auto candidatesLayout = new QGridLayout;
|
||||||
|
Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
|
||||||
|
oo.showFunctionSignatures = true;
|
||||||
|
oo.showReturnTypes = true;
|
||||||
|
for (int i = 0; i < m_candidates.size(); ++i) {
|
||||||
|
const auto implTargetComboBox = new QComboBox;
|
||||||
|
m_implTargetBoxes.append(implTargetComboBox);
|
||||||
|
implTargetComboBox->insertItems(0, implTargetStrings);
|
||||||
|
const Symbol * const func = m_candidates.at(i);
|
||||||
|
candidatesLayout->addWidget(new QLabel(oo.prettyType(func->type(), func->name())),
|
||||||
|
i, 0);
|
||||||
|
candidatesLayout->addWidget(implTargetComboBox, i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto buttonBox
|
||||||
|
= new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
|
||||||
|
defaultImplTargetComboBox->setCurrentIndex(implTargetStrings.size() - 1);
|
||||||
|
const auto mainLayout = new QVBoxLayout(this);
|
||||||
|
mainLayout->addLayout(defaultImplTargetLayout);
|
||||||
|
const auto separator = new QFrame();
|
||||||
|
separator->setFrameShape(QFrame::HLine);
|
||||||
|
mainLayout->addWidget(separator);
|
||||||
|
mainLayout->addLayout(candidatesLayout);
|
||||||
|
mainLayout->addWidget(buttonBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemberFunctionImplSettings settings() const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_candidates.size() == m_implTargetBoxes.size(), return {});
|
||||||
|
MemberFunctionImplSettings settings;
|
||||||
|
for (int i = 0; i < m_candidates.size(); ++i) {
|
||||||
|
MemberFunctionImplSetting setting;
|
||||||
|
const int index = m_implTargetBoxes.at(i)->currentIndex();
|
||||||
|
const bool addImplementation = index != 0;
|
||||||
|
if (!addImplementation)
|
||||||
|
continue;
|
||||||
|
setting.func = m_candidates.at(i);
|
||||||
|
setting.defPos = static_cast<DefPos>(index - 1);
|
||||||
|
settings << setting;
|
||||||
|
}
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QList<Symbol *> m_candidates;
|
||||||
|
QList<QComboBox *> m_implTargetBoxes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InsertDefsOperation: public CppQuickFixOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InsertDefsOperation(const CppQuickFixInterface &interface)
|
||||||
|
: CppQuickFixOperation(interface)
|
||||||
|
{
|
||||||
|
setDescription(CppQuickFixFactory::tr("Create Implementations for Member Functions"));
|
||||||
|
|
||||||
|
const QList<AST *> &path = interface.path();
|
||||||
|
if (path.size() < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Determine if cursor is on a class
|
||||||
|
const SimpleNameAST * const nameAST = path.at(path.size() - 1)->asSimpleName();
|
||||||
|
if (!nameAST || !interface.isCursorOn(nameAST))
|
||||||
|
return;
|
||||||
|
m_classAST = path.at(path.size() - 2)->asClassSpecifier();
|
||||||
|
if (!m_classAST)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Class * const theClass = m_classAST->symbol;
|
||||||
|
if (!theClass)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Collect all member functions without an implementation.
|
||||||
|
for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) {
|
||||||
|
Symbol * const s = *it;
|
||||||
|
if (!s->identifier() || !s->type() || !s->isDeclaration() || s->asFunction())
|
||||||
|
continue;
|
||||||
|
Function * const func = s->type()->asFunctionType();
|
||||||
|
if (!func || func->isSignal() || func->isFriend())
|
||||||
|
continue;
|
||||||
|
if (SymbolFinder().findMatchingDefinition(s, interface.snapshot()))
|
||||||
|
continue;
|
||||||
|
m_declarations << s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isApplicable() const { return !m_declarations.isEmpty(); }
|
||||||
|
void setMode(InsertDefsFromDecls::Mode mode) { m_mode = mode; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void perform() override
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!m_declarations.isEmpty(), return);
|
||||||
|
|
||||||
|
CppRefactoringChanges refactoring(snapshot());
|
||||||
|
const bool isHeaderFile = ProjectFile::isHeader(ProjectFile::classify(fileName()));
|
||||||
|
QString cppFile; // Only set if the class is defined in a header file.
|
||||||
|
if (isHeaderFile) {
|
||||||
|
InsertionPointLocator locator(refactoring);
|
||||||
|
for (const InsertionLocation &location
|
||||||
|
: locator.methodDefinition(m_declarations.first(), false, {})) {
|
||||||
|
if (!location.isValid())
|
||||||
|
continue;
|
||||||
|
const QString fileName = location.fileName();
|
||||||
|
if (ProjectFile::isHeader(ProjectFile::classify(fileName))) {
|
||||||
|
const QString source = CppTools::correspondingHeaderOrSource(fileName);
|
||||||
|
if (!source.isEmpty())
|
||||||
|
cppFile = source;
|
||||||
|
} else {
|
||||||
|
cppFile = fileName;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemberFunctionImplSettings settings;
|
||||||
|
switch (m_mode) {
|
||||||
|
case InsertDefsFromDecls::Mode::User: {
|
||||||
|
AddImplementationsDialog dlg(m_declarations, Utils::FilePath::fromString(cppFile));
|
||||||
|
if (dlg.exec() == QDialog::Accepted)
|
||||||
|
settings = dlg.settings();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InsertDefsFromDecls::Mode::Alternating: {
|
||||||
|
int defPos = DefPosImplementationFile;
|
||||||
|
const auto incDefPos = [&defPos] {
|
||||||
|
defPos = (defPos + 1) % (DefPosImplementationFile + 2);
|
||||||
|
};
|
||||||
|
for (Symbol * const func : qAsConst(m_declarations)) {
|
||||||
|
incDefPos();
|
||||||
|
if (defPos > DefPosImplementationFile)
|
||||||
|
continue;
|
||||||
|
MemberFunctionImplSetting setting;
|
||||||
|
setting.func = func;
|
||||||
|
setting.defPos = static_cast<DefPos>(defPos);
|
||||||
|
settings << setting;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InsertDefsFromDecls::Mode::Off:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
class DeclFinder : public ASTVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DeclFinder(const CppRefactoringFile *file, const Symbol *func)
|
||||||
|
: ASTVisitor(file->cppDocument()->translationUnit()), m_func(func) {}
|
||||||
|
|
||||||
|
SimpleDeclarationAST *decl() const { return m_decl; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool visit(SimpleDeclarationAST *decl) override
|
||||||
|
{
|
||||||
|
if (m_decl)
|
||||||
|
return false;
|
||||||
|
if (decl->symbols && decl->symbols->value == m_func)
|
||||||
|
m_decl = decl;
|
||||||
|
return !m_decl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Symbol * const m_func;
|
||||||
|
SimpleDeclarationAST *m_decl = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
QHash<QString, QPair<ChangeSet, QList<ChangeSet::Range>>> changeSets;
|
||||||
|
for (const MemberFunctionImplSetting &setting : qAsConst(settings)) {
|
||||||
|
DeclFinder finder(currentFile().data(), setting.func);
|
||||||
|
finder.accept(m_classAST);
|
||||||
|
QTC_ASSERT(finder.decl(), continue);
|
||||||
|
InsertionLocation loc;
|
||||||
|
const QString targetFilePath = setting.defPos == DefPosImplementationFile
|
||||||
|
? cppFile : fileName();
|
||||||
|
QTC_ASSERT(!targetFilePath.isEmpty(), continue);
|
||||||
|
if (setting.defPos == DefPosInsideClass) {
|
||||||
|
int line, column;
|
||||||
|
currentFile()->lineAndColumn(currentFile()->endOf(finder.decl()), &line, &column);
|
||||||
|
loc = InsertionLocation(fileName(), QString(), QString(), line, column);
|
||||||
|
}
|
||||||
|
auto &changeSet = changeSets[targetFilePath];
|
||||||
|
InsertDefOperation::insertDefinition(
|
||||||
|
this, loc, setting.defPos, finder.decl()->declarator_list->value,
|
||||||
|
setting.func->asDeclaration(),targetFilePath,
|
||||||
|
&changeSet.first, &changeSet.second);
|
||||||
|
}
|
||||||
|
for (auto it = changeSets.cbegin(); it != changeSets.cend(); ++it) {
|
||||||
|
const CppRefactoringFilePtr file = refactoring.file(it.key());
|
||||||
|
for (const ChangeSet::Range &r : it.value().second)
|
||||||
|
file->appendIndentRange(r);
|
||||||
|
file->setChangeSet(it.value().first);
|
||||||
|
file->apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassSpecifierAST *m_classAST = nullptr;
|
||||||
|
QList<Symbol *> m_declarations;
|
||||||
|
InsertDefsFromDecls::Mode m_mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void InsertDefsFromDecls::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
|
||||||
|
{
|
||||||
|
const auto op = QSharedPointer<InsertDefsOperation>::create(interface);
|
||||||
|
op->setMode(m_mode);
|
||||||
|
if (op->isApplicable())
|
||||||
|
result << op;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -7022,6 +7298,7 @@ void createCppQuickFixes()
|
|||||||
new InsertDeclFromDef;
|
new InsertDeclFromDef;
|
||||||
new InsertDefFromDecl;
|
new InsertDefFromDecl;
|
||||||
new InsertMemberFromInitialization;
|
new InsertMemberFromInitialization;
|
||||||
|
new InsertDefsFromDecls;
|
||||||
|
|
||||||
new MoveFuncDefOutside;
|
new MoveFuncDefOutside;
|
||||||
new MoveAllFuncDefOutside;
|
new MoveAllFuncDefOutside;
|
||||||
|
|||||||
@@ -407,6 +407,26 @@ private:
|
|||||||
const CPlusPlus::FunctionDefinitionAST *ctor) const;
|
const CPlusPlus::FunctionDefinitionAST *ctor) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Adds a definition for any number of member function declarations.
|
||||||
|
*/
|
||||||
|
class InsertDefsFromDecls : public CppQuickFixFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void match(const CppQuickFixInterface &interface,
|
||||||
|
TextEditor::QuickFixOperations &result) override;
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
Off, // Testing: simulates user canceling the dialog
|
||||||
|
Alternating, // Testing: simulates user choosing a different DefPos for every function
|
||||||
|
User // Normal interactive mode
|
||||||
|
};
|
||||||
|
void setMode(Mode mode) { m_mode = mode; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Mode m_mode = Mode::User;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Extracts the selected code and puts it to a function
|
Extracts the selected code and puts it to a function
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -550,9 +550,8 @@ static InsertionLocation nextToSurroundingDefinitions(Symbol *declaration,
|
|||||||
return InsertionLocation(QString::fromUtf8(definitionFunction->fileName()), prefix, suffix, line, column);
|
return InsertionLocation(QString::fromUtf8(definitionFunction->fileName()), prefix, suffix, line, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<InsertionLocation> InsertionPointLocator::methodDefinition(Symbol *declaration,
|
const QList<InsertionLocation> InsertionPointLocator::methodDefinition(
|
||||||
bool useSymbolFinder,
|
Symbol *declaration, bool useSymbolFinder, const QString &destinationFile) const
|
||||||
const QString &destinationFile) const
|
|
||||||
{
|
{
|
||||||
QList<InsertionLocation> result;
|
QList<InsertionLocation> result;
|
||||||
if (!declaration)
|
if (!declaration)
|
||||||
|
|||||||
@@ -93,9 +93,10 @@ public:
|
|||||||
const CPlusPlus::Class *clazz,
|
const CPlusPlus::Class *clazz,
|
||||||
AccessSpec xsSpec) const;
|
AccessSpec xsSpec) const;
|
||||||
|
|
||||||
QList<InsertionLocation> methodDefinition(CPlusPlus::Symbol *declaration,
|
const QList<InsertionLocation> methodDefinition(
|
||||||
bool useSymbolFinder = true,
|
CPlusPlus::Symbol *declaration,
|
||||||
const QString &destinationFile = QString()) const;
|
bool useSymbolFinder = true,
|
||||||
|
const QString &destinationFile = QString()) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CppRefactoringChanges m_refactoringChanges;
|
CppRefactoringChanges m_refactoringChanges;
|
||||||
|
|||||||
Reference in New Issue
Block a user