Commit 8f14bc0e authored by Christian Kamm's avatar Christian Kamm

C++: Synchronize function decl/def refactoring.

When editing a function declaration or definition the code model
may realize the same changes have to be applied somewhere else. A
refactoring marker will pop up that can be clicked to perform the
changes. Alternatively, press enter to apply.

Change-Id: I2299a2ecfb6a8f87d4853fc7cfa99486f890a1d3
Reviewed-on: http://codereview.qt.nokia.com/2909Reviewed-by: default avatarLeandro T. C. Melo <leandro.melo@nokia.com>
parent 13c8f9ea
......@@ -50,6 +50,7 @@
#include <NameVisitor.h>
#include <TypeVisitor.h>
#include <CoreTypes.h>
#include <LookupContext.h>
#include <QtCore/QByteArray>
#include <QtCore/QBitArray>
......@@ -583,6 +584,8 @@ void Document::check(CheckMode mode)
semantic(ast, _globalNamespace);
} else if (ExpressionAST *ast = _translationUnit->ast()->asExpression()) {
semantic(ast, _globalNamespace);
} else if (DeclarationAST *ast = translationUnit()->ast()->asDeclaration()) {
semantic(ast, _globalNamespace);
}
}
......@@ -972,7 +975,9 @@ public:
};
} // end of anonymous namespace
Symbol *Snapshot::findMatchingDefinition(Symbol *declaration) const
// strict means the returned symbol has to match exactly,
// including argument count and argument types
Symbol *Snapshot::findMatchingDefinition(Symbol *declaration, bool strict) const
{
if (!declaration)
return 0;
......@@ -1030,7 +1035,7 @@ Symbol *Snapshot::findMatchingDefinition(Symbol *declaration) const
if (viableFunctions.isEmpty())
continue;
else if (viableFunctions.length() == 1)
else if (! strict && viableFunctions.length() == 1)
return viableFunctions.first();
Function *best = 0;
......@@ -1039,7 +1044,7 @@ Symbol *Snapshot::findMatchingDefinition(Symbol *declaration) const
if (! (fun->unqualifiedName() && fun->unqualifiedName()->isEqualTo(declaration->unqualifiedName())))
continue;
else if (fun->argumentCount() == declarationTy->argumentCount()) {
if (! best)
if (! strict && ! best)
best = fun;
unsigned argc = 0;
......@@ -1055,7 +1060,7 @@ Symbol *Snapshot::findMatchingDefinition(Symbol *declaration) const
}
}
if (! best)
if (!strict && ! best)
best = viableFunctions.first();
return best;
......@@ -1089,3 +1094,74 @@ Class *Snapshot::findMatchingClassDeclaration(Symbol *declaration) const
return 0;
}
void CPlusPlus::findMatchingDeclaration(const LookupContext &context,
Function *functionType,
QList<Declaration *> *typeMatch,
QList<Declaration *> *argumentCountMatch,
QList<Declaration *> *nameMatch)
{
Scope *enclosingScope = functionType->enclosingScope();
while (! (enclosingScope->isNamespace() || enclosingScope->isClass()))
enclosingScope = enclosingScope->enclosingScope();
Q_ASSERT(enclosingScope != 0);
const Name *functionName = functionType->name();
if (! functionName)
return; // anonymous function names are not valid c++
ClassOrNamespace *binding = 0;
const QualifiedNameId *qName = functionName->asQualifiedNameId();
if (qName) {
if (qName->base())
binding = context.lookupType(qName->base(), enclosingScope);
functionName = qName->name();
}
if (!binding) { // declaration for a global function
binding = context.lookupType(enclosingScope);
if (!binding)
return;
}
const Identifier *funcId = functionName->identifier();
if (!funcId) // E.g. operator, which we might be able to handle in the future...
return;
foreach (Symbol *s, binding->symbols()) {
Class *matchingClass = s->asClass();
if (!matchingClass)
continue;
for (Symbol *s = matchingClass->find(funcId); s; s = s->next()) {
if (! s->name())
continue;
else if (! funcId->isEqualTo(s->identifier()))
continue;
else if (! s->type()->isFunctionType())
continue;
else if (Declaration *decl = s->asDeclaration()) {
if (Function *declFunTy = decl->type()->asFunctionType()) {
if (functionType->isEqualTo(declFunTy))
typeMatch->prepend(decl);
else if (functionType->argumentCount() == declFunTy->argumentCount())
argumentCountMatch->prepend(decl);
else
nameMatch->append(decl);
}
}
}
}
}
QList<Declaration *> CPlusPlus::findMatchingDeclaration(const LookupContext &context, Function *functionType)
{
QList<Declaration *> result;
QList<Declaration *> nameMatch, argumentCountMatch, typeMatch;
findMatchingDeclaration(context, functionType, &typeMatch, &argumentCountMatch, &nameMatch);
result.append(typeMatch);
result.append(argumentCountMatch);
result.append(nameMatch);
return result;
}
......@@ -45,6 +45,7 @@ namespace CPlusPlus {
class Macro;
class MacroArgumentReference;
class LookupContext;
class CPLUSPLUS_EXPORT Document
{
......@@ -386,7 +387,7 @@ public:
Document::Ptr documentFromSource(const QByteArray &preprocessedCode,
const QString &fileName) const;
Symbol *findMatchingDefinition(Symbol *symbol) const;
Symbol *findMatchingDefinition(Symbol *symbol, bool strict = false) const;
Class *findMatchingClassDeclaration(Symbol *symbol) const;
private:
......@@ -396,6 +397,15 @@ private:
_Base _documents;
};
void CPLUSPLUS_EXPORT findMatchingDeclaration(
const LookupContext &context,
Function *functionType,
QList<Declaration *> *typeMatch,
QList<Declaration *> *argumentCountMatch,
QList<Declaration *> *nameMatch);
QList<Declaration *> CPLUSPLUS_EXPORT findMatchingDeclaration(
const LookupContext &context, Function *functionType);
} // namespace CPlusPlus
#endif // CPLUSPLUS_CPPDOCUMENT_H
......@@ -41,6 +41,11 @@ ChangeSet::ChangeSet()
{
}
ChangeSet::ChangeSet(const QList<EditOp> &operations)
: m_string(0), m_cursor(0), m_operationList(operations), m_error(false)
{
}
static bool overlaps(int posA, int lengthA, int posB, int lengthB) {
if (lengthB > 0) {
return
......
......@@ -81,6 +81,7 @@ public:
public:
ChangeSet();
ChangeSet(const QList<EditOp> &operations);
bool isEmpty() const;
......
......@@ -67,6 +67,7 @@
#include <cpptools/cppcompletionassist.h>
#include <cpptools/cppqtstyleindenter.h>
#include <cpptools/cppcodestylesettings.h>
#include <cpptools/cpprefactoringchanges.h>
#include <coreplugin/icore.h>
#include <coreplugin/actionmanager/actionmanager.h>
......@@ -76,6 +77,7 @@
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/mimedatabase.h>
#include <utils/qtcassert.h>
#include <utils/uncommentselection.h>
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/projectexplorerconstants.h>
......@@ -84,6 +86,7 @@
#include <texteditor/fontsettings.h>
#include <texteditor/tabsettings.h>
#include <texteditor/texteditorconstants.h>
#include <texteditor/refactoroverlay.h>
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
#include <texteditor/codeassist/basicproposalitem.h>
#include <texteditor/codeassist/genericproposal.h>
......@@ -111,7 +114,8 @@
enum {
UPDATE_OUTLINE_INTERVAL = 500,
UPDATE_USES_INTERVAL = 500
UPDATE_USES_INTERVAL = 500,
UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL = 200
};
using namespace CPlusPlus;
......@@ -464,6 +468,13 @@ CPPEditorWidget::CPPEditorWidget(QWidget *parent)
m_referencesRevision = 0;
m_referencesCursorPosition = 0;
connect(&m_referencesWatcher, SIGNAL(finished()), SLOT(markSymbolsNow()));
connect(this, SIGNAL(refactorMarkerClicked(TextEditor::RefactorMarker)),
this, SLOT(onRefactorMarkerClicked(TextEditor::RefactorMarker)));
m_declDefLinkFinder = new FunctionDeclDefLinkFinder(this);
connect(m_declDefLinkFinder, SIGNAL(foundLink(QSharedPointer<FunctionDeclDefLink>)),
this, SLOT(onFunctionDeclDefLinkFound(QSharedPointer<FunctionDeclDefLink>)));
}
CPPEditorWidget::~CPPEditorWidget()
......@@ -533,6 +544,11 @@ void CPPEditorWidget::createToolBar(CPPEditor *editor)
m_updateUsesTimer->setInterval(UPDATE_USES_INTERVAL);
connect(m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow()));
m_updateFunctionDeclDefLinkTimer = new QTimer(this);
m_updateFunctionDeclDefLinkTimer->setSingleShot(true);
m_updateFunctionDeclDefLinkTimer->setInterval(UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL);
connect(m_updateFunctionDeclDefLinkTimer, SIGNAL(timeout()), this, SLOT(updateFunctionDeclDefLinkNow()));
connect(m_outlineCombo, SIGNAL(activated(int)), this, SLOT(jumpToOutlineElement(int)));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateOutlineIndex()));
connect(m_outlineCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOutlineToolTip()));
......@@ -540,6 +556,9 @@ void CPPEditorWidget::createToolBar(CPPEditor *editor)
connect(file(), SIGNAL(changed()), this, SLOT(updateFileName()));
// set up function declaration - definition link
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateFunctionDeclDefLink()));
connect(this, SIGNAL(textChanged()), this, SLOT(updateFunctionDeclDefLink()));
// set up the semantic highlighter
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateUses()));
......@@ -1170,77 +1189,6 @@ static inline LookupItem skipForwardDeclarations(const QList<LookupItem> &resolv
return result;
}
namespace {
QList<Declaration *> findMatchingDeclaration(const LookupContext &context,
Function *functionType)
{
QList<Declaration *> result;
Scope *enclosingScope = functionType->enclosingScope();
while (! (enclosingScope->isNamespace() || enclosingScope->isClass()))
enclosingScope = enclosingScope->enclosingScope();
Q_ASSERT(enclosingScope != 0);
const Name *functionName = functionType->name();
if (! functionName)
return result; // anonymous function names are not valid c++
ClassOrNamespace *binding = 0;
const QualifiedNameId *qName = functionName->asQualifiedNameId();
if (qName) {
if (qName->base())
binding = context.lookupType(qName->base(), enclosingScope);
functionName = qName->name();
}
if (!binding) { // declaration for a global function
binding = context.lookupType(enclosingScope);
if (!binding)
return result;
}
const Identifier *funcId = functionName->identifier();
if (!funcId) // E.g. operator, which we might be able to handle in the future...
return result;
QList<Declaration *> good, better, best;
foreach (Symbol *s, binding->symbols()) {
Class *matchingClass = s->asClass();
if (!matchingClass)
continue;
for (Symbol *s = matchingClass->find(funcId); s; s = s->next()) {
if (! s->name())
continue;
else if (! funcId->isEqualTo(s->identifier()))
continue;
else if (! s->type()->isFunctionType())
continue;
else if (Declaration *decl = s->asDeclaration()) {
if (Function *declFunTy = decl->type()->asFunctionType()) {
if (functionType->isEqualTo(declFunTy))
best.prepend(decl);
else if (functionType->argumentCount() == declFunTy->argumentCount() && result.isEmpty())
better.prepend(decl);
else
good.append(decl);
}
}
}
}
result.append(best);
result.append(better);
result.append(good);
return result;
}
} // end of anonymous namespace
CPPEditorWidget::Link CPPEditorWidget::attemptFuncDeclDef(const QTextCursor &cursor, const Document::Ptr &doc, Snapshot snapshot) const
{
snapshot.insert(doc);
......@@ -1616,7 +1564,10 @@ bool CPPEditorWidget::event(QEvent *e)
{
switch (e->type()) {
case QEvent::ShortcutOverride:
if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_currentRenameSelection != NoCurrentRenameSelection) {
// handle escape manually if a rename or func decl/def link is active
if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape
&& (m_currentRenameSelection != NoCurrentRenameSelection
|| m_declDefLink)) {
e->accept();
return true;
}
......@@ -1691,10 +1642,29 @@ void CPPEditorWidget::contextMenuEvent(QContextMenuEvent *e)
void CPPEditorWidget::keyPressEvent(QKeyEvent *e)
{
if (m_currentRenameSelection == NoCurrentRenameSelection) {
// key handling for linked function declarations/definitions
if (m_declDefLink && m_declDefLink->isMarkerVisible()) {
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
applyDeclDefLinkChanges(/*jump tp change*/ e->modifiers() & Qt::ShiftModifier);
e->accept();
return;
case Qt::Key_Escape:
abortDeclDefLink();
e->accept();
return;
default:
break;
}
}
TextEditor::BaseTextEditorWidget::keyPressEvent(e);
return;
}
// key handling for renames
QTextCursor cursor = textCursor();
const QTextCursor::MoveMode moveMode =
(e->modifiers() & Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
......@@ -1974,6 +1944,9 @@ void CPPEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo)
}
m_lastSemanticInfo.forced = false; // clear the forced flag
// schedule a check for a decl/def link
updateFunctionDeclDefLink();
}
namespace {
......@@ -2274,4 +2247,78 @@ TextEditor::IAssistInterface *CPPEditorWidget::createAssistInterface(
return 0;
}
void CPPEditorWidget::onRefactorMarkerClicked(const TextEditor::RefactorMarker &marker)
{
if (marker.data.canConvert<FunctionDeclDefLink::Marker>())
applyDeclDefLinkChanges(true);
}
void CPPEditorWidget::updateFunctionDeclDefLink()
{
const int pos = textCursor().selectionStart();
// if there's already a link, abort it if the cursor is outside
if (m_declDefLink
&& (pos < m_declDefLink->linkSelection.selectionStart()
|| pos > m_declDefLink->linkSelection.selectionEnd())) {
abortDeclDefLink();
return;
}
// don't start a new scan if there's one active and the cursor is already in the scanned area
const QTextCursor scannedSelection = m_declDefLinkFinder->scannedSelection();
if (!scannedSelection.isNull()
&& scannedSelection.selectionStart() <= pos
&& scannedSelection.selectionEnd() >= pos) {
return;
}
m_updateFunctionDeclDefLinkTimer->start();
}
void CPPEditorWidget::updateFunctionDeclDefLinkNow()
{
if (Core::EditorManager::instance()->currentEditor() != editor())
return;
if (m_declDefLink) {
// update the change marker
const Utils::ChangeSet changes = m_declDefLink->changes(m_lastSemanticInfo.snapshot);
if (changes.isEmpty())
m_declDefLink->hideMarker(this);
else
m_declDefLink->showMarker(this);
return;
}
if (!m_lastSemanticInfo.doc || isOutdated())
return;
Snapshot snapshot = CppModelManagerInterface::instance()->snapshot();
snapshot.insert(m_lastSemanticInfo.doc);
m_declDefLinkFinder->startFindLinkAt(textCursor(), m_lastSemanticInfo.doc, snapshot);
}
void CPPEditorWidget::onFunctionDeclDefLinkFound(QSharedPointer<FunctionDeclDefLink> link)
{
abortDeclDefLink();
m_declDefLink = link;
}
void CPPEditorWidget::applyDeclDefLinkChanges(bool jumpToMatch)
{
if (!m_declDefLink)
return;
m_declDefLink->apply(this, jumpToMatch);
m_declDefLink.clear();
updateFunctionDeclDefLink();
}
void CPPEditorWidget::abortDeclDefLink()
{
if (!m_declDefLink)
return;
m_declDefLink->hideMarker(this);
m_declDefLink.clear();
}
#include "cppeditor.moc"
......@@ -35,6 +35,7 @@
#include "cppeditorenums.h"
#include "cppsemanticinfo.h"
#include "cppfunctiondecldeflink.h"
#include <cplusplus/ModelManagerInterface.h>
#include <cplusplus/CppDocument.h>
......@@ -62,6 +63,7 @@ class Symbol;
namespace CppTools {
class CppModelManagerInterface;
class CppCodeStyleSettings;
class CppRefactoringFile;
}
namespace TextEditor {
......@@ -211,7 +213,7 @@ public Q_SLOTS:
protected:
bool event(QEvent *e);
void contextMenuEvent(QContextMenuEvent *);
void keyPressEvent(QKeyEvent *);
void keyPressEvent(QKeyEvent *e);
TextEditor::BaseTextEditor *createEditor();
......@@ -229,6 +231,9 @@ private Q_SLOTS:
void updateOutlineToolTip();
void updateUses();
void updateUsesNow();
void updateFunctionDeclDefLink();
void updateFunctionDeclDefLinkNow();
void onFunctionDeclDefLinkFound(QSharedPointer<FunctionDeclDefLink> link);
void onDocumentUpdated(CPlusPlus::Document::Ptr doc);
void onContentsChanged(int position, int charsRemoved, int charsAdded);
......@@ -241,6 +246,8 @@ private Q_SLOTS:
void performQuickFix(int index);
void onRefactorMarkerClicked(const TextEditor::RefactorMarker &marker);
private:
void markSymbols(const QTextCursor &tc, const SemanticInfo &info);
bool sortedOutline() const;
......@@ -261,6 +268,9 @@ private:
void finishRename();
void abortRename();
void applyDeclDefLinkChanges(bool jumpToMatch);
void abortDeclDefLink();
Link attemptFuncDeclDef(const QTextCursor &cursor,
const CPlusPlus::Document::Ptr &doc,
CPlusPlus::Snapshot snapshot) const;
......@@ -283,6 +293,7 @@ private:
QTimer *m_updateOutlineTimer;
QTimer *m_updateOutlineIndexTimer;
QTimer *m_updateUsesTimer;
QTimer *m_updateFunctionDeclDefLinkTimer;
QTextCharFormat m_occurrencesFormat;
QTextCharFormat m_occurrencesUnusedFormat;
QTextCharFormat m_occurrenceRenameFormat;
......@@ -315,6 +326,9 @@ private:
QFutureWatcher<QList<int> > m_referencesWatcher;
unsigned m_referencesRevision;
int m_referencesCursorPosition;
FunctionDeclDefLinkFinder *m_declDefLinkFinder;
QSharedPointer<FunctionDeclDefLink> m_declDefLink;
};
......
......@@ -26,7 +26,8 @@ HEADERS += cppplugin.h \
cppsnippetprovider.h \
cppinsertqtpropertymembers.h \
cppquickfixassistant.h \
cppquickfix.h
cppquickfix.h \
cppfunctiondecldeflink.h
SOURCES += cppplugin.cpp \
cppeditor.cpp \
......@@ -47,7 +48,10 @@ SOURCES += cppplugin.cpp \
cppsnippetprovider.cpp \
cppinsertqtpropertymembers.cpp \
cppquickfixassistant.cpp \
cppquickfix.cpp
cppquickfix.cpp \
cppfunctiondecldeflink.cpp
RESOURCES += cppeditor.qrc
OTHER_FILES += CppEditor.mimetypes.xml
This diff is collapsed.
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at info@qt.nokia.com.
**
**************************************************************************/
#ifndef CPPFUNCTIONDECLDEFLINK_H
#define CPPFUNCTIONDECLDEFLINK_H
#include <cplusplus/CppDocument.h>
#include <cplusplus/ASTfwd.h>
#include <cpptools/cpprefactoringchanges.h>
#include <utils/changeset.h>
#include <QtCore/QString>
#include <QtCore/QCoreApplication>
#include <QtCore/QSharedPointer>
#include <QtCore/QFutureWatcher>
#include <QtGui/QTextCursor>
namespace CppEditor {
namespace Internal {
class CPPEditorWidget;
class FunctionDeclDefLink;
class FunctionDeclDefLinkFinder : public QObject
{
Q_OBJECT
public:
FunctionDeclDefLinkFinder(QObject *parent = 0);
void startFindLinkAt(QTextCursor cursor,
const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot);
QTextCursor scannedSelection() const;
signals:
void foundLink(QSharedPointer<FunctionDeclDefLink> link);
private slots:
void onFutureDone();
private:
QTextCursor m_scannedSelection;
QFutureWatcher<QSharedPointer<FunctionDeclDefLink> > m_watcher;
};
class FunctionDeclDefLink
{
Q_DECLARE_TR_FUNCTIONS(FunctionDeclDefLink)
Q_DISABLE_COPY(FunctionDeclDefLink)
public:
~FunctionDeclDefLink();
class Marker {};
bool isValid() const;
bool isMarkerVisible() const;
void apply(CPPEditorWidget *editor, bool jumpToMatch);
void hideMarker(CPPEditorWidget *editor);
void showMarker(CPPEditorWidget *editor);
Utils::ChangeSet changes(const CPlusPlus::Snapshot &snapshot);
QTextCursor linkSelection;
int targetOffset;
QString targetInitial;
CPlusPlus::Document::Ptr sourceDocument;
CPlusPlus::Function *sourceFunction;
CPlusPlus::DeclarationAST *sourceDeclaration;
CPlusPlus::FunctionDeclaratorAST *sourceFunctionDeclarator;
QSharedPointer<CppTools::CppRefactoringFile> targetFile;
CPlusPlus::Function *targetFunction;
CPlusPlus::DeclarationAST *targetDeclaration;
CPlusPlus::FunctionDeclaratorAST *targetFunctionDeclarator;
private:
FunctionDeclDefLink();
bool hasMarker;
friend class FunctionDeclDefLinkFinder;
};
} // namespace Internal
} // namespace CppEditor
Q_DECLARE_METATYPE(CppEditor::Internal::FunctionDeclDefLink::Marker)
#endif // CPPFUNCTIONDECLDEFLINK_H
......@@ -138,7 +138,9 @@ CPPEditorWidget *CppQuickFixAssistInterface::editor() const
const CppRefactoringFile CppQuickFixAssistInterface::currentFile() const
{
return CppRefactoringFile(m_editor, m_semanticInfo.doc);
CppRefactoringFile file(m_editor);