forked from qt-creator/qt-creator
CppEditor: Add quickfix for moving a class to a dedicated set of files
Fixes: QTCREATORBUG-12190 Change-Id: I8d23525c132f086992f030e56789eea3f7b136c9 Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -111,7 +111,8 @@ void BuiltinModelManagerSupport::followSymbol(const CursorInEditor &data,
|
|||||||
SymbolFinder finder;
|
SymbolFinder finder;
|
||||||
m_followSymbol->findLink(data, processLinkCallback,
|
m_followSymbol->findLink(data, processLinkCallback,
|
||||||
resolveTarget, CppModelManager::snapshot(),
|
resolveTarget, CppModelManager::snapshot(),
|
||||||
data.editorWidget()->semanticInfo().doc, &finder, inNextSplit);
|
data.editorWidget() ? data.editorWidget()->semanticInfo().doc : data.cppDocument(),
|
||||||
|
&finder, inNextSplit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuiltinModelManagerSupport::followSymbolToType(const CursorInEditor &data,
|
void BuiltinModelManagerSupport::followSymbolToType(const CursorInEditor &data,
|
||||||
|
@@ -4,5 +4,42 @@
|
|||||||
<file>images/dark_qt_h.png</file>
|
<file>images/dark_qt_h.png</file>
|
||||||
<file>images/dark_qt_c.png</file>
|
<file>images/dark_qt_c.png</file>
|
||||||
<file>testcases/highlightingtestcase.cpp</file>
|
<file>testcases/highlightingtestcase.cpp</file>
|
||||||
|
<file>testcases/move-class/complex/complex.pro</file>
|
||||||
|
<file>testcases/move-class/complex/main.cpp</file>
|
||||||
|
<file>testcases/move-class/complex/main.cpp_expected</file>
|
||||||
|
<file>testcases/move-class/complex/theclass.cpp_expected</file>
|
||||||
|
<file>testcases/move-class/complex/theheader.h</file>
|
||||||
|
<file>testcases/move-class/complex/theheader.h_expected</file>
|
||||||
|
<file>testcases/move-class/complex/thesource.cpp</file>
|
||||||
|
<file>testcases/move-class/complex/thesource.cpp_expected</file>
|
||||||
|
<file>testcases/move-class/complex/complex.pro_expected</file>
|
||||||
|
<file>testcases/move-class/nested/main.cpp</file>
|
||||||
|
<file>testcases/move-class/nested/nested.pro</file>
|
||||||
|
<file>testcases/move-class/match1/match1.pro</file>
|
||||||
|
<file>testcases/move-class/match1/TheClass.h</file>
|
||||||
|
<file>testcases/move-class/match2/match2.pro</file>
|
||||||
|
<file>testcases/move-class/match2/theclass.h</file>
|
||||||
|
<file>testcases/move-class/match3/match3.pro</file>
|
||||||
|
<file>testcases/move-class/match3/the_class.h</file>
|
||||||
|
<file>testcases/move-class/single/single.pro</file>
|
||||||
|
<file>testcases/move-class/single/theheader.h</file>
|
||||||
|
<file>testcases/move-class/header-only/header-only.pro</file>
|
||||||
|
<file>testcases/move-class/header-only/header-only.pro_expected</file>
|
||||||
|
<file>testcases/move-class/header-only/theclass.h_expected</file>
|
||||||
|
<file>testcases/move-class/header-only/theheader.h</file>
|
||||||
|
<file>testcases/move-class/header-only/thesource.cpp</file>
|
||||||
|
<file>testcases/move-class/header-only/theheader.h_expected</file>
|
||||||
|
<file>testcases/move-class/header-only/thesource.cpp_expected</file>
|
||||||
|
<file>testcases/move-class/decl-in-source/decl-in-source.pro</file>
|
||||||
|
<file>testcases/move-class/decl-in-source/decl-in-source.pro_expected</file>
|
||||||
|
<file>testcases/move-class/decl-in-source/theclass.h_expected</file>
|
||||||
|
<file>testcases/move-class/decl-in-source/thesource.cpp</file>
|
||||||
|
<file>testcases/move-class/decl-in-source/thesource.cpp_expected</file>
|
||||||
|
<file>testcases/move-class/template/template.pro</file>
|
||||||
|
<file>testcases/move-class/template/template.pro_expected</file>
|
||||||
|
<file>testcases/move-class/template/theclass.h_expected</file>
|
||||||
|
<file>testcases/move-class/template/theheader.h</file>
|
||||||
|
<file>testcases/move-class/template/theheader.h_expected</file>
|
||||||
|
<file>testcases/move-class/complex/theclass.h_expected</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@@ -43,6 +43,7 @@ using CPlusPlus::Document;
|
|||||||
// FIXME: Clean up the namespaces
|
// FIXME: Clean up the namespaces
|
||||||
using CppEditor::Tests::ModelManagerTestHelper;
|
using CppEditor::Tests::ModelManagerTestHelper;
|
||||||
using CppEditor::Tests::ProjectOpenerAndCloser;
|
using CppEditor::Tests::ProjectOpenerAndCloser;
|
||||||
|
using CppEditor::Tests::SourceFilesRefreshGuard;
|
||||||
using CppEditor::Tests::TemporaryCopiedDir;
|
using CppEditor::Tests::TemporaryCopiedDir;
|
||||||
using CppEditor::Tests::TemporaryDir;
|
using CppEditor::Tests::TemporaryDir;
|
||||||
using CppEditor::Tests::TestCase;
|
using CppEditor::Tests::TestCase;
|
||||||
@@ -987,30 +988,6 @@ void ModelManagerTest::testRenameIncludes_data()
|
|||||||
<< "subdir2/header2.h" << "subdir1/header2_moved.h" << false;
|
<< "subdir2/header2.h" << "subdir1/header2_moved.h" << false;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SourceFilesRefreshGuard : public QObject
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SourceFilesRefreshGuard()
|
|
||||||
{
|
|
||||||
connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, this, [this] {
|
|
||||||
m_refreshed = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() { m_refreshed = false; }
|
|
||||||
bool wait()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 10 && !m_refreshed; ++i) {
|
|
||||||
CppEditor::Tests::waitForSignalOrTimeout(
|
|
||||||
CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, 1000);
|
|
||||||
}
|
|
||||||
return m_refreshed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool m_refreshed = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
void ModelManagerTest::testRenameIncludes()
|
void ModelManagerTest::testRenameIncludes()
|
||||||
{
|
{
|
||||||
// Set up project.
|
// Set up project.
|
||||||
|
@@ -12,6 +12,9 @@
|
|||||||
#include "cppsourceprocessertesthelper.h"
|
#include "cppsourceprocessertesthelper.h"
|
||||||
#include "cpptoolssettings.h"
|
#include "cpptoolssettings.h"
|
||||||
|
|
||||||
|
#include <projectexplorer/kitmanager.h>
|
||||||
|
#include <projectexplorer/projectexplorer.h>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@@ -23,6 +26,7 @@
|
|||||||
*/
|
*/
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
using namespace CPlusPlus;
|
using namespace CPlusPlus;
|
||||||
|
using namespace ProjectExplorer;
|
||||||
using namespace TextEditor;
|
using namespace TextEditor;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
@@ -9948,4 +9952,100 @@ void QuickfixTest::testConvertToMetaMethodInvocation()
|
|||||||
QuickFixOperationTest({CppTestDocument::create("file.cpp", input, expected)}, &factory);
|
QuickFixOperationTest({CppTestDocument::create("file.cpp", input, expected)}, &factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QuickfixTest::testMoveClassToOwnFile_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("projectName");
|
||||||
|
QTest::addColumn<QString>("fileName");
|
||||||
|
QTest::addColumn<QString>("className");
|
||||||
|
QTest::addColumn<bool>("applicable");
|
||||||
|
|
||||||
|
QTest::newRow("nested") << "nested" << "main.cpp" << "Inner" << false;
|
||||||
|
QTest::newRow("file name match 1") << "match1" << "TheClass.h" << "TheClass" << false;
|
||||||
|
QTest::newRow("file name match 2") << "match2" << "theclass.h" << "TheClass" << false;
|
||||||
|
QTest::newRow("file name match 3") << "match3" << "the_class.h" << "TheClass" << false;
|
||||||
|
QTest::newRow("single") << "single" << "theheader.h" << "TheClass" << false;
|
||||||
|
QTest::newRow("complex") << "complex" << "theheader.h" << "TheClass" << true;
|
||||||
|
QTest::newRow("header only") << "header-only" << "theheader.h" << "TheClass" << true;
|
||||||
|
QTest::newRow("decl in source file") << "decl-in-source" << "thesource.cpp" << "TheClass" << true;
|
||||||
|
QTest::newRow("template") << "template" << "theheader.h" << "TheClass" << true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickfixTest::testMoveClassToOwnFile()
|
||||||
|
{
|
||||||
|
QFETCH(QString, projectName);
|
||||||
|
QFETCH(QString, fileName);
|
||||||
|
QFETCH(QString, className);
|
||||||
|
QFETCH(bool, applicable);
|
||||||
|
using namespace CppEditor::Tests;
|
||||||
|
|
||||||
|
// Set up project.
|
||||||
|
Kit * const kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) {
|
||||||
|
return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid();
|
||||||
|
});
|
||||||
|
if (!kit)
|
||||||
|
QSKIP("The test requires at least one valid kit with a valid Qt");
|
||||||
|
const auto projectDir = std::make_unique<TemporaryCopiedDir>(
|
||||||
|
":/cppeditor/testcases/move-class/" + projectName);
|
||||||
|
SourceFilesRefreshGuard refreshGuard;
|
||||||
|
ProjectOpenerAndCloser projectMgr;
|
||||||
|
QVERIFY(projectMgr.open(projectDir->absolutePath(projectName + ".pro"), true, kit));
|
||||||
|
QVERIFY(refreshGuard.wait());
|
||||||
|
|
||||||
|
// Open header file and locate class.
|
||||||
|
const auto headerFilePath = projectDir->absolutePath(fileName);
|
||||||
|
QVERIFY2(headerFilePath.exists(), qPrintable(headerFilePath.toUserOutput()));
|
||||||
|
const auto editor = qobject_cast<BaseTextEditor *>(EditorManager::openEditor(headerFilePath));
|
||||||
|
QVERIFY(editor);
|
||||||
|
const auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document());
|
||||||
|
QVERIFY(doc);
|
||||||
|
QTextCursor classCursor = doc->document()->find("class " + className);
|
||||||
|
QVERIFY(!classCursor.isNull());
|
||||||
|
editor->setCursorPosition(classCursor.position());
|
||||||
|
const auto editorWidget = qobject_cast<CppEditorWidget *>(editor->editorWidget());
|
||||||
|
QVERIFY(editorWidget);
|
||||||
|
QVERIFY(TestCase::waitForRehighlightedSemanticDocument(editorWidget));
|
||||||
|
|
||||||
|
// Query factory.
|
||||||
|
MoveClassToOwnFile factory;
|
||||||
|
factory.setNonInteractive();
|
||||||
|
CppQuickFixInterface quickFixInterface(editorWidget, ExplicitlyInvoked);
|
||||||
|
QuickFixOperations operations;
|
||||||
|
factory.match(quickFixInterface, operations);
|
||||||
|
QCOMPARE(operations.isEmpty(), !applicable);
|
||||||
|
if (!applicable)
|
||||||
|
return;
|
||||||
|
operations.first()->perform();
|
||||||
|
QVERIFY(waitForSignalOrTimeout(doc, &IDocument::saved, 30000));
|
||||||
|
QTest::qWait(1000);
|
||||||
|
|
||||||
|
// Compare all files.
|
||||||
|
const FileFilter filter({"*_expected"}, QDir::Files);
|
||||||
|
const FilePaths expectedDocuments = projectDir->filePath().dirEntries(filter);
|
||||||
|
QVERIFY(!expectedDocuments.isEmpty());
|
||||||
|
for (const FilePath &expected : expectedDocuments) {
|
||||||
|
static const QString suffix = "_expected";
|
||||||
|
const FilePath actual = expected.parentDir()
|
||||||
|
.pathAppended(expected.fileName().chopped(suffix.length()));
|
||||||
|
QVERIFY(actual.exists());
|
||||||
|
const auto actualContents = actual.fileContents();
|
||||||
|
QVERIFY(actualContents);
|
||||||
|
const auto expectedContents = expected.fileContents();
|
||||||
|
const QByteArrayList actualLines = actualContents->split('\n');
|
||||||
|
const QByteArrayList expectedLines = expectedContents->split('\n');
|
||||||
|
if (actualLines.size() != expectedLines.size()) {
|
||||||
|
qDebug().noquote().nospace() << "---\n" << *expectedContents << "EOF";
|
||||||
|
qDebug().noquote().nospace() << "+++\n" << *actualContents << "EOF";
|
||||||
|
}
|
||||||
|
QCOMPARE(actualLines.size(), expectedLines.size());
|
||||||
|
for (int i = 0; i < actualLines.size(); ++i) {
|
||||||
|
const QByteArray actualLine = actualLines.at(i);
|
||||||
|
const QByteArray expectedLine = expectedLines.at(i);
|
||||||
|
if (actualLine != expectedLine)
|
||||||
|
qDebug() << "Unexpected content in line" << (i + 1) << "of file"
|
||||||
|
<< actual.fileName();
|
||||||
|
QCOMPARE(actualLine, expectedLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace CppEditor::Internal::Tests
|
} // namespace CppEditor::Internal::Tests
|
||||||
|
@@ -229,6 +229,9 @@ private slots:
|
|||||||
|
|
||||||
void testConvertToMetaMethodInvocation_data();
|
void testConvertToMetaMethodInvocation_data();
|
||||||
void testConvertToMetaMethodInvocation();
|
void testConvertToMetaMethodInvocation();
|
||||||
|
|
||||||
|
void testMoveClassToOwnFile_data();
|
||||||
|
void testMoveClassToOwnFile();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Tests
|
} // namespace Tests
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#include "cppeditordocument.h"
|
#include "cppeditordocument.h"
|
||||||
#include "cppeditortr.h"
|
#include "cppeditortr.h"
|
||||||
#include "cppeditorwidget.h"
|
#include "cppeditorwidget.h"
|
||||||
|
#include "cppfilesettingspage.h"
|
||||||
#include "cppfunctiondecldeflink.h"
|
#include "cppfunctiondecldeflink.h"
|
||||||
#include "cppinsertvirtualmethods.h"
|
#include "cppinsertvirtualmethods.h"
|
||||||
#include "cpplocatordata.h"
|
#include "cpplocatordata.h"
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
#include "symbolfinder.h"
|
#include "symbolfinder.h"
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <coreplugin/messagebox.h>
|
#include <coreplugin/messagemanager.h>
|
||||||
|
|
||||||
#include <cplusplus/ASTPath.h>
|
#include <cplusplus/ASTPath.h>
|
||||||
#include <cplusplus/CPlusPlusForwardDeclarations.h>
|
#include <cplusplus/CPlusPlusForwardDeclarations.h>
|
||||||
@@ -43,11 +44,14 @@
|
|||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
#include <utils/basetreeview.h>
|
#include <utils/basetreeview.h>
|
||||||
|
#include <utils/codegeneration.h>
|
||||||
#include <utils/layoutbuilder.h>
|
#include <utils/layoutbuilder.h>
|
||||||
#include <utils/fancylineedit.h>
|
#include <utils/fancylineedit.h>
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
|
#include <utils/pathchooser.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/treemodel.h>
|
#include <utils/treemodel.h>
|
||||||
|
#include <utils/treeviewcombobox.h>
|
||||||
|
|
||||||
#ifdef WITH_TESTS
|
#ifdef WITH_TESTS
|
||||||
#include <QAbstractItemModelTester>
|
#include <QAbstractItemModelTester>
|
||||||
@@ -84,6 +88,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace CPlusPlus;
|
using namespace CPlusPlus;
|
||||||
|
using namespace ProjectExplorer;
|
||||||
using namespace TextEditor;
|
using namespace TextEditor;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
@@ -10030,6 +10035,571 @@ void ConvertToMetaMethodCall::doMatch(const CppQuickFixInterface &interface,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class MoveClassToOwnFileOp : public CppQuickFixOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MoveClassToOwnFileOp(
|
||||||
|
const CppQuickFixInterface &interface,
|
||||||
|
AST *fullDecl,
|
||||||
|
ClassSpecifierAST *classAst,
|
||||||
|
const QList<Namespace *> &namespacePath,
|
||||||
|
bool interactive)
|
||||||
|
: CppQuickFixOperation(interface)
|
||||||
|
, m_state(std::make_shared<State>())
|
||||||
|
{
|
||||||
|
setDescription(Tr::tr("Move class to a dedicated set of source files"));
|
||||||
|
m_state->originalFilePath = interface.currentFile()->filePath();
|
||||||
|
m_state->classAst = classAst;
|
||||||
|
m_state->namespacePath = namespacePath;
|
||||||
|
m_state->interactive = interactive;
|
||||||
|
PerFileState &perFileState = m_state->perFileState[interface.currentFile()->filePath()];
|
||||||
|
perFileState.refactoringFile = interface.currentFile();
|
||||||
|
perFileState.declarationsToMove << fullDecl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PerFileState {
|
||||||
|
// We want to keep the relative order of moved code.
|
||||||
|
void insertSorted(AST *decl) {
|
||||||
|
declarationsToMove.insert(std::lower_bound(
|
||||||
|
declarationsToMove.begin(),
|
||||||
|
declarationsToMove.end(),
|
||||||
|
decl,
|
||||||
|
[](const AST *elem, const AST *value) {
|
||||||
|
return elem->firstToken() < value->firstToken();
|
||||||
|
}), decl);
|
||||||
|
}
|
||||||
|
|
||||||
|
CppRefactoringFilePtr refactoringFile;
|
||||||
|
QList<AST *> declarationsToMove;
|
||||||
|
};
|
||||||
|
struct State {
|
||||||
|
using Ptr = std::shared_ptr<State>;
|
||||||
|
|
||||||
|
FilePath originalFilePath;
|
||||||
|
AST *fullDecl = nullptr;
|
||||||
|
ClassSpecifierAST *classAst = nullptr;
|
||||||
|
QList<Namespace *> namespacePath;
|
||||||
|
Links lookupResults;
|
||||||
|
QMap<FilePath, PerFileState> perFileState; // A map for deterministic order of moved code.
|
||||||
|
CppRefactoringChanges factory{CppModelManager::snapshot()};
|
||||||
|
int remainingFollowSymbolOps = 0;
|
||||||
|
bool interactive = true;
|
||||||
|
};
|
||||||
|
class Dialog : public QDialog {
|
||||||
|
public:
|
||||||
|
Dialog(const FilePath &defaultHeaderFilePath, const FilePath &defaultSourceFilePath,
|
||||||
|
ProjectNode *defaultProjectNode)
|
||||||
|
: m_buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
|
||||||
|
{
|
||||||
|
ProjectNode * const rootNode = defaultProjectNode
|
||||||
|
? defaultProjectNode->getProject()->rootProjectNode()
|
||||||
|
: nullptr;
|
||||||
|
if (rootNode) {
|
||||||
|
const auto projectRootItem = new NodeItem(rootNode);
|
||||||
|
buildTree(projectRootItem);
|
||||||
|
m_projectModel.rootItem()->appendChild(projectRootItem);
|
||||||
|
}
|
||||||
|
m_projectNodeComboBox.setModel(&m_projectModel);
|
||||||
|
if (defaultProjectNode) {
|
||||||
|
const auto matcher = [defaultProjectNode](TreeItem *item) {
|
||||||
|
return static_cast<NodeItem *>(item)->node == defaultProjectNode;
|
||||||
|
};
|
||||||
|
TreeItem * const defaultItem = m_projectModel.rootItem()->findAnyChild(matcher);
|
||||||
|
if (defaultItem ) {
|
||||||
|
QModelIndex index = m_projectModel.indexForItem(defaultItem);
|
||||||
|
m_projectNodeComboBox.setCurrentIndex(index);
|
||||||
|
while (index.isValid()) {
|
||||||
|
m_projectNodeComboBox.view()->expand(index);
|
||||||
|
index = index.parent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
connect(&m_projectNodeComboBox, &QComboBox::currentIndexChanged,
|
||||||
|
this, [this] {
|
||||||
|
if (m_filesEdited)
|
||||||
|
return;
|
||||||
|
const auto newProjectNode = projectNode();
|
||||||
|
QTC_ASSERT(newProjectNode, return);
|
||||||
|
const FilePath baseDir = newProjectNode->directory();
|
||||||
|
m_sourcePathChooser.setFilePath(
|
||||||
|
baseDir.pathAppended(sourceFilePath().fileName()));
|
||||||
|
m_headerPathChooser.setFilePath(
|
||||||
|
baseDir.pathAppended(headerFilePath().fileName()));
|
||||||
|
m_filesEdited = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
m_headerOnlyCheckBox.setText(Tr::tr("Header file only"));
|
||||||
|
m_headerOnlyCheckBox.setChecked(false);
|
||||||
|
connect(&m_headerOnlyCheckBox, &QCheckBox::toggled,
|
||||||
|
this, [this](bool checked) { m_sourcePathChooser.setEnabled(!checked); });
|
||||||
|
|
||||||
|
m_headerPathChooser.setExpectedKind(PathChooser::SaveFile);
|
||||||
|
m_sourcePathChooser.setExpectedKind(PathChooser::SaveFile);
|
||||||
|
m_headerPathChooser.setFilePath(defaultHeaderFilePath);
|
||||||
|
m_sourcePathChooser.setFilePath(defaultSourceFilePath);
|
||||||
|
connect(&m_headerPathChooser, &PathChooser::textChanged,
|
||||||
|
this, [this] { m_filesEdited = true; });
|
||||||
|
connect(&m_sourcePathChooser, &PathChooser::textChanged,
|
||||||
|
this, [this] { m_filesEdited = true; });
|
||||||
|
|
||||||
|
connect(&m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(&m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
|
||||||
|
using namespace Layouting;
|
||||||
|
Column {
|
||||||
|
Form {
|
||||||
|
Tr::tr("Project:"), &m_projectNodeComboBox, br,
|
||||||
|
&m_headerOnlyCheckBox, br,
|
||||||
|
Tr::tr("Header file:"), &m_headerPathChooser, br,
|
||||||
|
Tr::tr("Implementation file:"), &m_sourcePathChooser, br,
|
||||||
|
},
|
||||||
|
&m_buttonBox
|
||||||
|
}.attachTo(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectNode *projectNode() const
|
||||||
|
{
|
||||||
|
const QVariant v = m_projectNodeComboBox.currentData(Qt::UserRole);
|
||||||
|
return v.isNull() ? nullptr : static_cast<ProjectNode *>(v.value<void *>());
|
||||||
|
}
|
||||||
|
bool createSourceFile() const { return !m_headerOnlyCheckBox.isChecked(); }
|
||||||
|
FilePath headerFilePath() const { return m_headerPathChooser.absoluteFilePath(); }
|
||||||
|
FilePath sourceFilePath() const { return m_sourcePathChooser.absoluteFilePath(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct NodeItem : public StaticTreeItem {
|
||||||
|
NodeItem(ProjectNode *node)
|
||||||
|
: StaticTreeItem({node->displayName()}, {node->directory().toUserOutput()})
|
||||||
|
, node(node)
|
||||||
|
{}
|
||||||
|
Qt::ItemFlags flags(int) const override
|
||||||
|
{
|
||||||
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||||
|
}
|
||||||
|
QVariant data(int column, int role) const override
|
||||||
|
{
|
||||||
|
if (role == Qt::UserRole)
|
||||||
|
return QVariant::fromValue(static_cast<void *>(node));
|
||||||
|
return StaticTreeItem::data(column, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectNode * const node;
|
||||||
|
};
|
||||||
|
|
||||||
|
void buildTree(NodeItem *parent)
|
||||||
|
{
|
||||||
|
for (Node * const node : parent->node->nodes()) {
|
||||||
|
if (const auto projNode = node->asProjectNode()) {
|
||||||
|
const auto child = new NodeItem(projNode);
|
||||||
|
buildTree(child);
|
||||||
|
parent->appendChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeViewComboBox m_projectNodeComboBox;
|
||||||
|
QCheckBox m_headerOnlyCheckBox;
|
||||||
|
PathChooser m_headerPathChooser;
|
||||||
|
PathChooser m_sourcePathChooser;
|
||||||
|
QDialogButtonBox m_buttonBox;
|
||||||
|
TreeModel<> m_projectModel;
|
||||||
|
bool m_filesEdited = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
void perform() override
|
||||||
|
{
|
||||||
|
collectImplementations(m_state->classAst->symbol, m_state);
|
||||||
|
if (m_state->remainingFollowSymbolOps == 0)
|
||||||
|
finish(m_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CppRefactoringFilePtr getRefactoringFile(const FilePath &filePath, const State::Ptr &state)
|
||||||
|
{
|
||||||
|
CppRefactoringFilePtr &refactoringFile = state->perFileState[filePath].refactoringFile;
|
||||||
|
if (refactoringFile)
|
||||||
|
return refactoringFile;
|
||||||
|
CppEditorWidget *editorWidget = nullptr;
|
||||||
|
const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForFilePath(filePath);
|
||||||
|
for (Core::IEditor *editor : editors) {
|
||||||
|
const auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor);
|
||||||
|
if (textEditor)
|
||||||
|
editorWidget = qobject_cast<CppEditorWidget *>(textEditor->editorWidget());
|
||||||
|
if (editorWidget)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
refactoringFile = editorWidget
|
||||||
|
? state->factory.file(editorWidget, editorWidget->semanticInfo().doc)
|
||||||
|
: state->factory.cppFile(filePath);
|
||||||
|
return refactoringFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lookupSymbol(Symbol *symbol, const State::Ptr &state)
|
||||||
|
{
|
||||||
|
const CppRefactoringFilePtr refactoringFile = getRefactoringFile(symbol->filePath(), state);
|
||||||
|
const auto editorWidget = qobject_cast<CppEditorWidget *>(refactoringFile->editor());
|
||||||
|
QTextCursor cursor(refactoringFile->document()->begin());
|
||||||
|
TranslationUnit * const tu = refactoringFile->cppDocument()->translationUnit();
|
||||||
|
const int symbolPos = tu->getTokenPositionInDocument(symbol->sourceLocation(),
|
||||||
|
refactoringFile->document());
|
||||||
|
cursor.setPosition(symbolPos);
|
||||||
|
const CursorInEditor cursorInEditor(
|
||||||
|
cursor,
|
||||||
|
symbol->filePath(),
|
||||||
|
editorWidget,
|
||||||
|
editorWidget ? editorWidget->textDocument() : nullptr,
|
||||||
|
refactoringFile->cppDocument());
|
||||||
|
const auto callback = [symbol, symbolPos, doc = cursor.document(), state](const Link &link) {
|
||||||
|
class FinishedChecker {
|
||||||
|
public:
|
||||||
|
FinishedChecker(const State::Ptr &state) : m_state(state) {}
|
||||||
|
~FinishedChecker() {
|
||||||
|
if (--m_state->remainingFollowSymbolOps == 0)
|
||||||
|
finish(m_state);
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
const State::Ptr &m_state;
|
||||||
|
} finishedChecker(state);
|
||||||
|
if (!link.hasValidTarget())
|
||||||
|
return;
|
||||||
|
if (symbol->filePath() == link.targetFilePath) {
|
||||||
|
const int linkPos = Text::positionInText(doc, link.targetLine,
|
||||||
|
link.targetColumn + 1);
|
||||||
|
if (linkPos == symbolPos)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const CppRefactoringFilePtr refactoringFile
|
||||||
|
= getRefactoringFile(link.targetFilePath, state);
|
||||||
|
const QList<AST *> astPath = ASTPath(
|
||||||
|
refactoringFile->cppDocument())(link.targetLine, link.targetColumn);
|
||||||
|
const bool isTemplate = symbol->asTemplate();
|
||||||
|
const bool isFunction = symbol->type()->asFunctionType();
|
||||||
|
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
|
||||||
|
const bool match = isTemplate ? bool((*it)->asTemplateDeclaration())
|
||||||
|
: isFunction ? bool((*it)->asFunctionDefinition())
|
||||||
|
: bool((*it)->asSimpleDeclaration());
|
||||||
|
if (match) {
|
||||||
|
// For member functions of class templates.
|
||||||
|
if (isFunction) {
|
||||||
|
const auto next = std::next(it);
|
||||||
|
if (next != astPath.rend() && (*next)->asTemplateDeclaration())
|
||||||
|
it = next;
|
||||||
|
}
|
||||||
|
state->perFileState[link.targetFilePath].insertSorted(*it);
|
||||||
|
if (symbol->asForwardClassDeclaration()) {
|
||||||
|
if (const auto classSpec = (*(it - 1))->asClassSpecifier();
|
||||||
|
classSpec && classSpec->symbol) {
|
||||||
|
collectImplementations(classSpec->symbol, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
++state->remainingFollowSymbolOps;
|
||||||
|
|
||||||
|
// Force queued execution, as the built-in editor can run the callback synchronously.
|
||||||
|
const auto followSymbol = [cursorInEditor, callback] {
|
||||||
|
CppModelManager::followSymbol(
|
||||||
|
cursorInEditor, callback, true, false, FollowSymbolMode::Exact);
|
||||||
|
};
|
||||||
|
QMetaObject::invokeMethod(CppModelManager::instance(), followSymbol, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void collectImplementations(Class *klass, const State::Ptr &state)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < klass->memberCount(); ++i) {
|
||||||
|
Symbol * const member = klass->memberAt(i);
|
||||||
|
if (member->asForwardClassDeclaration() || member->asTemplate()) {
|
||||||
|
lookupSymbol(member, state);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto decl = member->asDeclaration();
|
||||||
|
if (!decl)
|
||||||
|
continue;
|
||||||
|
if (decl->type().type()->asFunctionType()) {
|
||||||
|
if (!decl->asFunction())
|
||||||
|
lookupSymbol(member, state);
|
||||||
|
} else if (decl->isStatic() && !decl->type().isInline()) {
|
||||||
|
lookupSymbol(member, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void finish(const State::Ptr &state)
|
||||||
|
{
|
||||||
|
Overview ov;
|
||||||
|
Project * const project = ProjectManager::projectForFile(state->originalFilePath);
|
||||||
|
const CppFileSettings fileSettings = cppFileSettingsForProject(project);
|
||||||
|
const auto constructDefaultFilePaths = [&] {
|
||||||
|
const QString className = ov.prettyName(state->classAst->symbol->name());
|
||||||
|
const QString baseFileName = fileSettings.lowerCaseFiles ? className.toLower() : className;
|
||||||
|
const QString headerFileName = baseFileName + '.' + fileSettings.headerSuffix;
|
||||||
|
const FilePath baseDir = state->originalFilePath.parentDir();
|
||||||
|
const FilePath headerFilePath = baseDir.pathAppended(headerFileName);
|
||||||
|
const QString sourceFileName = baseFileName + '.' + fileSettings.sourceSuffix;
|
||||||
|
const FilePath sourceFilePath = baseDir.pathAppended(sourceFileName);
|
||||||
|
return std::make_pair(headerFilePath, sourceFilePath);
|
||||||
|
};
|
||||||
|
auto [headerFilePath, sourceFilePath] = constructDefaultFilePaths();
|
||||||
|
bool mustCreateSourceFile = false;
|
||||||
|
bool mustNotCreateSourceFile = false;
|
||||||
|
ProjectNode *projectNode = nullptr;
|
||||||
|
if (project && project->rootProjectNode()) {
|
||||||
|
const Node * const origNode = project->nodeForFilePath(state->originalFilePath);
|
||||||
|
if (origNode)
|
||||||
|
projectNode = const_cast<Node *>(origNode)->managingProject();
|
||||||
|
}
|
||||||
|
if (state->interactive) {
|
||||||
|
Dialog dlg(headerFilePath, sourceFilePath, projectNode);
|
||||||
|
if (dlg.exec() != QDialog::Accepted)
|
||||||
|
return;
|
||||||
|
projectNode = dlg.projectNode();
|
||||||
|
headerFilePath = dlg.headerFilePath();
|
||||||
|
sourceFilePath = dlg.sourceFilePath();
|
||||||
|
mustCreateSourceFile = dlg.createSourceFile();
|
||||||
|
mustNotCreateSourceFile = !dlg.createSourceFile();
|
||||||
|
}
|
||||||
|
const auto fileListForDisplay = [](const FilePaths &files) {
|
||||||
|
return Utils::transform<QStringList>(files, [](const FilePath &fp) {
|
||||||
|
return '"' + fp.toUserOutput() + '"';
|
||||||
|
}).join(", ");
|
||||||
|
};
|
||||||
|
FilePaths existingFiles;
|
||||||
|
if (headerFilePath.exists())
|
||||||
|
existingFiles << headerFilePath;
|
||||||
|
if (!mustNotCreateSourceFile && sourceFilePath.exists())
|
||||||
|
existingFiles << sourceFilePath;
|
||||||
|
if (!existingFiles.isEmpty()) {
|
||||||
|
Core::MessageManager::writeDisrupting(
|
||||||
|
Tr::tr("Refusing to overwrite the following files: %1\n")
|
||||||
|
.arg(fileListForDisplay(existingFiles)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QString headerFileName = headerFilePath.fileName();
|
||||||
|
|
||||||
|
QString headerContent;
|
||||||
|
QString sourceContent;
|
||||||
|
QList<QString *> commonContent{&headerContent};
|
||||||
|
if (!mustNotCreateSourceFile)
|
||||||
|
commonContent << &sourceContent;
|
||||||
|
for (QString *const content : std::as_const(commonContent)) {
|
||||||
|
content->append(fileSettings.licenseTemplate());
|
||||||
|
if (!content->isEmpty())
|
||||||
|
content->append('\n');
|
||||||
|
}
|
||||||
|
sourceContent.append('\n').append("#include \"").append(headerFileName).append("\"\n");
|
||||||
|
const QStringList namespaceNames
|
||||||
|
= Utils::transform<QStringList>(state->namespacePath, [&](const Namespace *ns) {
|
||||||
|
return ov.prettyName(ns->name());
|
||||||
|
});
|
||||||
|
const QString headerGuard = Utils::headerGuard(headerFileName, namespaceNames);
|
||||||
|
if (fileSettings.headerPragmaOnce) {
|
||||||
|
headerContent.append("#pragma once\n");
|
||||||
|
} else {
|
||||||
|
headerContent.append("#ifndef " + headerGuard + "\n");
|
||||||
|
headerContent.append("#define " + headerGuard + "\n");
|
||||||
|
}
|
||||||
|
if (!namespaceNames.isEmpty()) {
|
||||||
|
for (QString *const content : std::as_const(commonContent)) {
|
||||||
|
content->append('\n');
|
||||||
|
for (const QString &ns : namespaceNames)
|
||||||
|
content->append("namespace " + ns + " {\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool hasSourceContent = false;
|
||||||
|
for (auto it = state->perFileState.begin(); it != state->perFileState.end(); ++it) {
|
||||||
|
if (it->declarationsToMove.isEmpty())
|
||||||
|
continue;
|
||||||
|
const CppRefactoringFilePtr refactoringFile = it->refactoringFile;
|
||||||
|
QTC_ASSERT(refactoringFile, continue);
|
||||||
|
const bool isDeclFile = refactoringFile->filePath() == state->originalFilePath;
|
||||||
|
ChangeSet changes;
|
||||||
|
if (isDeclFile) {
|
||||||
|
QString relInclude = headerFilePath.relativePathFrom(
|
||||||
|
refactoringFile->filePath().parentDir()).toString();
|
||||||
|
if (!relInclude.isEmpty())
|
||||||
|
relInclude.append('/');
|
||||||
|
relInclude.append('"').append(headerFileName).append('"');
|
||||||
|
insertNewIncludeDirective(relInclude, refactoringFile,
|
||||||
|
refactoringFile->cppDocument(), changes);
|
||||||
|
}
|
||||||
|
for (AST * const declToMove : std::as_const(it->declarationsToMove)) {
|
||||||
|
const ChangeSet::Range rangeToMove = refactoringFile->range(declToMove);
|
||||||
|
QString &content = isDeclFile || mustNotCreateSourceFile ? headerContent
|
||||||
|
: sourceContent;
|
||||||
|
if (&content == &sourceContent)
|
||||||
|
hasSourceContent = true;
|
||||||
|
content.append('\n')
|
||||||
|
.append(refactoringFile->textOf(rangeToMove))
|
||||||
|
.append('\n');
|
||||||
|
changes.remove(rangeToMove);
|
||||||
|
}
|
||||||
|
refactoringFile->setChangeSet(changes);
|
||||||
|
refactoringFile->apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!namespaceNames.isEmpty()) {
|
||||||
|
for (QString *const content : std::as_const(commonContent)) {
|
||||||
|
content->append('\n');
|
||||||
|
for (auto it = namespaceNames.rbegin(); it != namespaceNames.rend(); ++it)
|
||||||
|
content->append("} // namespace " + *it + '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fileSettings.headerPragmaOnce)
|
||||||
|
headerContent.append("\n#endif // " + headerGuard + '\n');
|
||||||
|
|
||||||
|
CppRefactoringFilePtr headerFile = state->factory.cppFile(headerFilePath);
|
||||||
|
headerFilePath.ensureExistingFile();
|
||||||
|
ChangeSet headerChanges;
|
||||||
|
headerChanges.insert(0, headerContent);
|
||||||
|
headerFile->setChangeSet(headerChanges);
|
||||||
|
headerFile->apply();
|
||||||
|
if (hasSourceContent || mustCreateSourceFile) {
|
||||||
|
sourceFilePath.ensureExistingFile();
|
||||||
|
CppRefactoringFilePtr sourceFile = state->factory.cppFile(sourceFilePath);
|
||||||
|
ChangeSet sourceChanges;
|
||||||
|
sourceChanges.insert(0, sourceContent);
|
||||||
|
sourceFile->setChangeSet(sourceChanges);
|
||||||
|
sourceFile->apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!projectNode)
|
||||||
|
return;
|
||||||
|
FilePaths toAdd{headerFilePath};
|
||||||
|
if (hasSourceContent)
|
||||||
|
toAdd << sourceFilePath;
|
||||||
|
FilePaths notAdded;
|
||||||
|
projectNode->addFiles(toAdd, ¬Added);
|
||||||
|
if (!notAdded.isEmpty()) {
|
||||||
|
Core::MessageManager::writeDisrupting(
|
||||||
|
Tr::tr("Failed to add to project file \"%1\": %2")
|
||||||
|
.arg(projectNode->filePath().toUserOutput(), fileListForDisplay(notAdded)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->interactive)
|
||||||
|
Core::EditorManager::openEditor(headerFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const State::Ptr m_state;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Applies if and only if:
|
||||||
|
// - Class is not a nested class.
|
||||||
|
// - Class name does not match file name via any of the usual transformations.
|
||||||
|
// - There are other declarations in the same file.
|
||||||
|
void MoveClassToOwnFile::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
|
||||||
|
{
|
||||||
|
ClassSpecifierAST * const classAst = astForClassOperations(interface);
|
||||||
|
if (!classAst || !classAst->symbol)
|
||||||
|
return;
|
||||||
|
AST *fullDecl = nullptr;
|
||||||
|
for (auto it = interface.path().rbegin(); it != interface.path().rend() && !fullDecl; ++it) {
|
||||||
|
if (*it == classAst && it != interface.path().rend() - 1) {
|
||||||
|
auto next = std::next(it);
|
||||||
|
fullDecl = (*next)->asSimpleDeclaration();
|
||||||
|
if (next != interface.path().rend() - 1) {
|
||||||
|
next = std::next(next);
|
||||||
|
if (const auto templ = (*next)->asTemplateDeclaration())
|
||||||
|
fullDecl = templ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fullDecl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check file name.
|
||||||
|
const QString className = Overview().prettyName(classAst->symbol->name());
|
||||||
|
if (className.isEmpty())
|
||||||
|
return;
|
||||||
|
const QString lowerFileBaseName = interface.filePath().baseName().toLower();
|
||||||
|
if (lowerFileBaseName.contains(className.toLower()))
|
||||||
|
return;
|
||||||
|
QString underscoredClassName = className;
|
||||||
|
QChar curChar = underscoredClassName.at(0);
|
||||||
|
for (int i = 1; i < underscoredClassName.size(); ++i) {
|
||||||
|
const QChar prevChar = curChar;
|
||||||
|
curChar = underscoredClassName.at(i);
|
||||||
|
if (curChar.isUpper() && prevChar.isLetterOrNumber() && !prevChar.isUpper()) {
|
||||||
|
underscoredClassName.insert(i, '_');
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lowerFileBaseName.contains(underscoredClassName.toLower()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Is there more than one class definition in the file?
|
||||||
|
AST * const ast = interface.currentFile()->cppDocument()->translationUnit()->ast();
|
||||||
|
if (!ast)
|
||||||
|
return;
|
||||||
|
DeclarationListAST * const topLevelDecls = ast->asTranslationUnit()->declaration_list;
|
||||||
|
if (!topLevelDecls)
|
||||||
|
return;
|
||||||
|
QList<Namespace *> namespacePath;
|
||||||
|
QList<Namespace *> currentNamespacePath;
|
||||||
|
bool foundOtherDecls = false;
|
||||||
|
bool foundSelf = false;
|
||||||
|
std::function<void(Namespace *)> collectSymbolsFromNamespace;
|
||||||
|
const auto handleSymbol = [&](Symbol *symbol) {
|
||||||
|
if (!symbol)
|
||||||
|
return;
|
||||||
|
if (const auto nsMember = symbol->asNamespace()) {
|
||||||
|
collectSymbolsFromNamespace(nsMember);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (symbol != classAst->symbol) {
|
||||||
|
if (!symbol->asForwardClassDeclaration())
|
||||||
|
foundOtherDecls = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QTC_ASSERT(symbol->asClass(), return);
|
||||||
|
foundSelf = true;
|
||||||
|
namespacePath = currentNamespacePath;
|
||||||
|
};
|
||||||
|
collectSymbolsFromNamespace = [&](Namespace *ns) {
|
||||||
|
currentNamespacePath << ns;
|
||||||
|
for (int i = 0; i < ns->memberCount() && (!foundSelf || !foundOtherDecls); ++i)
|
||||||
|
handleSymbol(ns->memberAt(i));
|
||||||
|
currentNamespacePath.removeLast();
|
||||||
|
};
|
||||||
|
for (DeclarationListAST *it = topLevelDecls; it && (!foundSelf || !foundOtherDecls);
|
||||||
|
it = it->next) {
|
||||||
|
DeclarationAST *decl = it->value;
|
||||||
|
if (!decl)
|
||||||
|
continue;
|
||||||
|
if (const auto templ = decl->asTemplateDeclaration())
|
||||||
|
decl = templ->declaration;
|
||||||
|
if (!decl)
|
||||||
|
continue;
|
||||||
|
if (const auto ns = decl->asNamespace(); ns && ns->symbol) {
|
||||||
|
collectSymbolsFromNamespace(ns->symbol);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const auto simpleDecl = decl->asSimpleDeclaration()) {
|
||||||
|
if (!simpleDecl->decl_specifier_list)
|
||||||
|
continue;
|
||||||
|
for (SpecifierListAST *spec = simpleDecl->decl_specifier_list; spec; spec = spec->next) {
|
||||||
|
if (!spec->value)
|
||||||
|
continue;
|
||||||
|
if (const auto klass = spec->value->asClassSpecifier())
|
||||||
|
handleSymbol(klass->symbol);
|
||||||
|
else if (!spec->value->asElaboratedTypeSpecifier()) // forward decl
|
||||||
|
foundOtherDecls = true;
|
||||||
|
}
|
||||||
|
} else if (decl->asDeclaration()) {
|
||||||
|
foundOtherDecls = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundSelf && foundOtherDecls) {
|
||||||
|
result << new MoveClassToOwnFileOp(
|
||||||
|
interface, fullDecl, classAst, namespacePath, m_interactive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void createCppQuickFixes()
|
void createCppQuickFixes()
|
||||||
{
|
{
|
||||||
new AddIncludeForUndefinedIdentifier;
|
new AddIncludeForUndefinedIdentifier;
|
||||||
@@ -10090,6 +10660,7 @@ void createCppQuickFixes()
|
|||||||
new ConvertCommentStyle;
|
new ConvertCommentStyle;
|
||||||
new MoveFunctionComments;
|
new MoveFunctionComments;
|
||||||
new ConvertToMetaMethodCall;
|
new ConvertToMetaMethodCall;
|
||||||
|
new MoveClassToOwnFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroyCppQuickFixes()
|
void destroyCppQuickFixes()
|
||||||
|
@@ -626,5 +626,19 @@ private:
|
|||||||
TextEditor::QuickFixOperations &result) override;
|
TextEditor::QuickFixOperations &result) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//! Move a class into a dedicates set of files.
|
||||||
|
class MoveClassToOwnFile : public CppQuickFixFactory
|
||||||
|
{
|
||||||
|
#ifdef WITH_TESTS
|
||||||
|
public:
|
||||||
|
void setNonInteractive() { m_interactive = false; }
|
||||||
|
#endif
|
||||||
|
private:
|
||||||
|
void doMatch(const CppQuickFixInterface &interface,
|
||||||
|
TextEditor::QuickFixOperations &result) override;
|
||||||
|
|
||||||
|
bool m_interactive = true;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace CppEditor
|
} // namespace CppEditor
|
||||||
|
@@ -510,4 +510,19 @@ int clangdIndexingTimeout()
|
|||||||
return intervalAsInt;
|
return intervalAsInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SourceFilesRefreshGuard::SourceFilesRefreshGuard()
|
||||||
|
{
|
||||||
|
connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, this, [this] {
|
||||||
|
m_refreshed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SourceFilesRefreshGuard::wait()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10 && !m_refreshed; ++i) {
|
||||||
|
CppEditor::Tests::waitForSignalOrTimeout(
|
||||||
|
CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, 1000);
|
||||||
|
}
|
||||||
|
return m_refreshed;
|
||||||
|
}
|
||||||
} // namespace CppEditor::Tests
|
} // namespace CppEditor::Tests
|
||||||
|
@@ -205,5 +205,17 @@ private:
|
|||||||
TemporaryCopiedDir();
|
TemporaryCopiedDir();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SourceFilesRefreshGuard : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SourceFilesRefreshGuard();
|
||||||
|
|
||||||
|
void reset() { m_refreshed = false; }
|
||||||
|
bool wait();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_refreshed = false;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Tests
|
} // namespace Tests
|
||||||
} // namespace CppEditor
|
} // namespace CppEditor
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cplusplus/CppDocument.h>
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
#include <utils/link.h>
|
#include <utils/link.h>
|
||||||
|
|
||||||
@@ -23,14 +24,17 @@ class CursorInEditor
|
|||||||
public:
|
public:
|
||||||
CursorInEditor(const QTextCursor &cursor, const Utils::FilePath &filePath,
|
CursorInEditor(const QTextCursor &cursor, const Utils::FilePath &filePath,
|
||||||
CppEditorWidget *editorWidget = nullptr,
|
CppEditorWidget *editorWidget = nullptr,
|
||||||
TextEditor::TextDocument *textDocument = nullptr)
|
TextEditor::TextDocument *textDocument = nullptr,
|
||||||
|
const CPlusPlus::Document::Ptr &cppDocument = {})
|
||||||
: m_cursor(cursor)
|
: m_cursor(cursor)
|
||||||
, m_filePath(filePath)
|
, m_filePath(filePath)
|
||||||
, m_editorWidget(editorWidget)
|
, m_editorWidget(editorWidget)
|
||||||
, m_textDocument(textDocument)
|
, m_textDocument(textDocument)
|
||||||
|
, m_cppDocument(cppDocument)
|
||||||
{}
|
{}
|
||||||
CppEditorWidget *editorWidget() const { return m_editorWidget; }
|
CppEditorWidget *editorWidget() const { return m_editorWidget; }
|
||||||
TextEditor::TextDocument *textDocument() const { return m_textDocument; }
|
TextEditor::TextDocument *textDocument() const { return m_textDocument; }
|
||||||
|
CPlusPlus::Document::Ptr cppDocument() const { return m_cppDocument; }
|
||||||
const QTextCursor &cursor() const { return m_cursor; }
|
const QTextCursor &cursor() const { return m_cursor; }
|
||||||
const Utils::FilePath &filePath() const { return m_filePath; }
|
const Utils::FilePath &filePath() const { return m_filePath; }
|
||||||
private:
|
private:
|
||||||
@@ -38,6 +42,7 @@ private:
|
|||||||
Utils::FilePath m_filePath;
|
Utils::FilePath m_filePath;
|
||||||
CppEditorWidget *m_editorWidget = nullptr;
|
CppEditorWidget *m_editorWidget = nullptr;
|
||||||
TextEditor::TextDocument * const m_textDocument;
|
TextEditor::TextDocument * const m_textDocument;
|
||||||
|
const CPlusPlus::Document::Ptr m_cppDocument;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace CppEditor
|
} // namespace CppEditor
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
HEADERS = theheader.h
|
||||||
|
SOURCES = thesource.cpp main.cpp
|
@@ -0,0 +1,4 @@
|
|||||||
|
HEADERS = theheader.h \
|
||||||
|
theclass.h
|
||||||
|
SOURCES = thesource.cpp main.cpp \
|
||||||
|
theclass.cpp
|
11
src/plugins/cppeditor/testcases/move-class/complex/main.cpp
Normal file
11
src/plugins/cppeditor/testcases/move-class/complex/main.cpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include "theheader.h"
|
||||||
|
|
||||||
|
namespace Project {
|
||||||
|
namespace Internal {
|
||||||
|
bool TheClass::needsDefinition = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
#include "theheader.h"
|
||||||
|
|
||||||
|
namespace Project {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
}
|
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
#include "theclass.h"
|
||||||
|
|
||||||
|
namespace Project {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
bool TheClass::needsDefinition = true;
|
||||||
|
|
||||||
|
class TheClass::Private
|
||||||
|
{
|
||||||
|
void func();
|
||||||
|
int m_member = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TheClass::defined() {}
|
||||||
|
|
||||||
|
void TheClass::Private::func() {}
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace Project
|
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef PROJECT_INTERNAL_THECLASS_H
|
||||||
|
#define PROJECT_INTERNAL_THECLASS_H
|
||||||
|
|
||||||
|
namespace Project {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class TheClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TheClass() = default;
|
||||||
|
|
||||||
|
void defined();
|
||||||
|
void undefined();
|
||||||
|
|
||||||
|
template<typename T> T defaultValue() const;
|
||||||
|
private:
|
||||||
|
class Private;
|
||||||
|
class Undefined;
|
||||||
|
static inline bool doesNotNeedDefinition = true;
|
||||||
|
static bool needsDefinition;
|
||||||
|
int m_value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T> T TheClass::defaultValue() const { return T(); }
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace Project
|
||||||
|
|
||||||
|
#endif // PROJECT_INTERNAL_THECLASS_H
|
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef THE_HEADER_H
|
||||||
|
#define THE_HEADER_H
|
||||||
|
|
||||||
|
namespace Project {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class SomeClass {};
|
||||||
|
class TheClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TheClass() = default;
|
||||||
|
|
||||||
|
void defined();
|
||||||
|
void undefined();
|
||||||
|
|
||||||
|
template<typename T> T defaultValue() const;
|
||||||
|
private:
|
||||||
|
class Private;
|
||||||
|
class Undefined;
|
||||||
|
static inline bool doesNotNeedDefinition = true;
|
||||||
|
static bool needsDefinition;
|
||||||
|
int m_value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T> T TheClass::defaultValue() const { return T(); }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef THE_HEADER_H
|
||||||
|
#define THE_HEADER_H
|
||||||
|
|
||||||
|
#include "theclass.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Project {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class SomeClass {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -0,0 +1,19 @@
|
|||||||
|
#include "theheader.h"
|
||||||
|
|
||||||
|
namespace Project {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
static void someFunction() {}
|
||||||
|
|
||||||
|
class TheClass::Private
|
||||||
|
{
|
||||||
|
void func();
|
||||||
|
int m_member = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TheClass::defined() {}
|
||||||
|
|
||||||
|
void TheClass::Private::func() {}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
#include "theheader.h"
|
||||||
|
|
||||||
|
namespace Project {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
static void someFunction() {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,2 @@
|
|||||||
|
HEADERS = theheader.h
|
||||||
|
SOURCES = thesource.cpp
|
@@ -0,0 +1,3 @@
|
|||||||
|
HEADERS = theheader.h \
|
||||||
|
theclass.h
|
||||||
|
SOURCES = thesource.cpp
|
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef THECLASS_H
|
||||||
|
#define THECLASS_H
|
||||||
|
|
||||||
|
class TheClass
|
||||||
|
{
|
||||||
|
void func();
|
||||||
|
};
|
||||||
|
|
||||||
|
void TheClass::func() {}
|
||||||
|
|
||||||
|
#endif // THECLASS_H
|
@@ -0,0 +1,4 @@
|
|||||||
|
class SomeClass
|
||||||
|
{
|
||||||
|
void func();
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
#include "theheader.h"
|
||||||
|
|
||||||
|
class TheClass
|
||||||
|
{
|
||||||
|
void func();
|
||||||
|
};
|
||||||
|
void SomeClass::func() {}
|
||||||
|
void TheClass::func() {}
|
@@ -0,0 +1,6 @@
|
|||||||
|
#include "theclass.h"
|
||||||
|
#include "theheader.h"
|
||||||
|
|
||||||
|
|
||||||
|
void SomeClass::func() {}
|
||||||
|
|
@@ -0,0 +1,2 @@
|
|||||||
|
HEADERS = theheader.h
|
||||||
|
SOURCES = thesource.cpp
|
@@ -0,0 +1,3 @@
|
|||||||
|
HEADERS = theheader.h \
|
||||||
|
theclass.h
|
||||||
|
SOURCES = thesource.cpp
|
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef THECLASS_H
|
||||||
|
#define THECLASS_H
|
||||||
|
|
||||||
|
class TheClass {
|
||||||
|
void func();
|
||||||
|
};
|
||||||
|
|
||||||
|
void TheClass::func() {}
|
||||||
|
|
||||||
|
#endif // THECLASS_H
|
@@ -0,0 +1,7 @@
|
|||||||
|
class SomeClass {
|
||||||
|
void func();
|
||||||
|
};
|
||||||
|
class TheClass {
|
||||||
|
void func();
|
||||||
|
};
|
||||||
|
void TheClass::func() {}
|
@@ -0,0 +1,7 @@
|
|||||||
|
#include "theclass.h"
|
||||||
|
|
||||||
|
class SomeClass {
|
||||||
|
void func();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@@ -0,0 +1,3 @@
|
|||||||
|
#include "theheader.h"
|
||||||
|
|
||||||
|
void SomeClass::func() {}
|
@@ -0,0 +1,3 @@
|
|||||||
|
#include "theheader.h"
|
||||||
|
|
||||||
|
void SomeClass::func() {}
|
@@ -0,0 +1,2 @@
|
|||||||
|
class SomeClass {};
|
||||||
|
class TheClass {};
|
@@ -0,0 +1 @@
|
|||||||
|
HEADERS = TheClass.h
|
@@ -0,0 +1 @@
|
|||||||
|
HEADERS = theclass.h
|
@@ -0,0 +1,2 @@
|
|||||||
|
class SomeClass {};
|
||||||
|
class TheClass {};
|
@@ -0,0 +1 @@
|
|||||||
|
HEADERS = the_class.h
|
@@ -0,0 +1,2 @@
|
|||||||
|
class SomeClass {};
|
||||||
|
class TheClass {};
|
@@ -0,0 +1,6 @@
|
|||||||
|
class SomeClass {};
|
||||||
|
class Outer {
|
||||||
|
class Inner {};
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {}
|
@@ -0,0 +1 @@
|
|||||||
|
SOURCES = main.cpp
|
@@ -0,0 +1 @@
|
|||||||
|
HEADERS = theheader.h
|
@@ -0,0 +1,2 @@
|
|||||||
|
class Private;
|
||||||
|
class TheClass {};
|
@@ -0,0 +1 @@
|
|||||||
|
HEADERS = theheader.h
|
@@ -0,0 +1,2 @@
|
|||||||
|
HEADERS = theheader.h \
|
||||||
|
theclass.h
|
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef THECLASS_H
|
||||||
|
#define THECLASS_H
|
||||||
|
|
||||||
|
template<typename T> class TheClass
|
||||||
|
{
|
||||||
|
T defaultValue() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T> inline T TheClass<T>::defaultValue() const { return T(); }
|
||||||
|
|
||||||
|
#endif // THECLASS_H
|
@@ -0,0 +1,7 @@
|
|||||||
|
enum SomeEnum { E };
|
||||||
|
template<typename T> class TheClass
|
||||||
|
{
|
||||||
|
T defaultValue() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T> inline T TheClass<T>::defaultValue() const { return T(); }
|
@@ -0,0 +1,6 @@
|
|||||||
|
#include "theclass.h"
|
||||||
|
|
||||||
|
enum SomeEnum { E };
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user